mirror of
https://github.com/GnoConsortium/gno.git
synced 2025-02-01 22:31:43 +00:00
51b2bbcf92
Increased the number of characters in the name field of the nameEntry struct from 34 to 36. This gives an 8-byte alignment to the offset entry, which provides binary compatibility of the precompiled database file between the IIgs and little-endian 32-bit machines such as Linux boxes. *** THIS MEANS THAT THE PRECOMPILED DATABASE IS NO LONGER BINARY COMPATIBLE WITH EARLIER VERSIONS. Updated descu so that the "Last Updated" line in the describe source "header" is updated to reflect the current date and time (GMT, if supported). Fixed a bug in descu that caused the "header" to be slowly eaten, one character at a time. The describe database source file is no longer part of this archive. It is available separately from the url, above. Added a "Known Bugs" section and URLs on where to find the online database to this README. Modified the installation instructions a bit.
638 lines
15 KiB
C
638 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.7 1998/01/17 07:20:07 gdr 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 MAX_BUFFER 65534
|
|
#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 > MAX_BUFFER) {
|
|
fprintf(stderr,"descu internal error: cannot handle files greater "
|
|
"than %d bytes\n due to a compiler bug. Sorry.\n",
|
|
MAX_BUFFER);
|
|
exit(-1);
|
|
}
|
|
lseek(fd,(off_t) 0, SEEK_SET);
|
|
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;
|
|
}
|