/* * 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 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.5 1997/10/30 04:19:10 gdr Exp $ */ #include #include #include #include #include #include #include #include #include #include #ifdef __GNO__ #include #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: is the path name of the file to read in * * Post: returns a malloc'd NULL-terminated buffer containing the contents * of file . 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 consisting of 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 (jname,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 cs0 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 ab. * 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 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) { char *buffer; int i, j; FILE *outfp, *rejfp, *temp = NULL; int c; char *outputfile=NULL; 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++; optindname, 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 (iname,entryArray1[i]->data); i++; } while (jname,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; }