1 /* texindex -- sort TeX index dribble output into an actual index.
2    $Id: texindex.c,v 1.11 2004/04/11 17:56:47 karl Exp $
3 
4    Copyright (C) 1987, 1991, 1992, 1996, 1997, 1998, 1999, 2000, 2001,
5    2002, 2003, 2004 Free Software Foundation, Inc.
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2, or (at your option)
10    any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307. */
20 
21 #include "system.h"
22 #include <getopt.h>
23 
24 __RCSID("$MirOS: src/gnu/usr.bin/texinfo/util/texindex.c,v 1.2 2005/03/13 15:19:46 tg Exp $");
25 
26 static char *program_name = "texindex";
27 
28 #if defined (emacs)
29 #  include "../src/config.h"
30 /* Some s/os.h files redefine these. */
31 #  undef read
32 #  undef close
33 #  undef write
34 #  undef open
35 #endif
36 
37 #if !defined (HAVE_MEMSET)
38 #undef memset
39 #define memset(ptr, ignore, count) bzero (ptr, count)
40 #endif
41 
42 char *mktemp (char *);
43 
44 #if !defined (SEEK_SET)
45 #  define SEEK_SET 0
46 #  define SEEK_CUR 1
47 #  define SEEK_END 2
48 #endif /* !SEEK_SET */
49 
50 struct linebuffer;
51 
52 /* When sorting in core, this structure describes one line
53    and the position and length of its first keyfield.  */
54 struct lineinfo
55 {
56   char *text;           /* The actual text of the line. */
57   union {
58     char *text;         /* The start of the key (for textual comparison). */
59     long number;        /* The numeric value (for numeric comparison). */
60   } key;
61   long keylen;          /* Length of KEY field. */
62 };
63 
64 /* This structure describes a field to use as a sort key. */
65 struct keyfield
66 {
67   int startwords;       /* Number of words to skip. */
68   int startchars;       /* Number of additional chars to skip. */
69   int endwords;         /* Number of words to ignore at end. */
70   int endchars;         /* Ditto for characters of last word. */
71   char ignore_blanks;   /* Non-zero means ignore spaces and tabs. */
72   char fold_case;       /* Non-zero means case doesn't matter. */
73   char reverse;         /* Non-zero means compare in reverse order. */
74   char numeric;         /* Non-zeros means field is ASCII numeric. */
75   char positional;      /* Sort according to file position. */
76   char braced;          /* Count balanced-braced groupings as fields. */
77 };
78 
79 /* Vector of keyfields to use. */
80 struct keyfield keyfields[3];
81 
82 /* Number of keyfields stored in that vector.  */
83 int num_keyfields = 3;
84 
85 /* Vector of input file names, terminated with a null pointer. */
86 char **infiles;
87 
88 /* Vector of corresponding output file names, or NULL, meaning default it
89    (add an `s' to the end). */
90 char **outfiles;
91 
92 /* Length of `infiles'. */
93 int num_infiles;
94 
95 /* Pointer to the array of pointers to lines being sorted. */
96 char **linearray;
97 
98 /* The allocated length of `linearray'. */
99 long nlines;
100 
101 /* Directory to use for temporary files.  On Unix, it ends with a slash.  */
102 char *tempdir;
103 
104 /* Number of last temporary file.  */
105 int tempcount;
106 
107 /* Number of last temporary file already deleted.
108    Temporary files are deleted by `flush_tempfiles' in order of creation.  */
109 int last_deleted_tempcount;
110 
111 /* During in-core sort, this points to the base of the data block
112    which contains all the lines of data.  */
113 char *text_base;
114 
115 /* Initially 0; changed to 1 if we want initials in this index.  */
116 int need_initials;
117 
118 /* Remembers the first initial letter seen in this index, so we can
119    determine whether we need initials in the sorted form.  */
120 char first_initial;
121 
122 /* Additional command switches .*/
123 
124 /* Nonzero means do not delete tempfiles -- for debugging. */
125 int keep_tempfiles;
126 
127 /* Forward declarations of functions in this file. */
128 void decode_command (int argc, char **argv);
129 void sort_in_core (char *infile, int total, char *outfile);
130 void sort_offline (char *infile, off_t total, char *outfile);
131 char **parsefile (char *filename, char **nextline, char *data, long int size);
132 char *find_field (struct keyfield *keyfield, char *str, long int *lengthptr);
133 char *find_pos (char *str, int words, int chars, int ignore_blanks);
134 long find_value (char *start, long int length);
135 char *find_braced_pos (char *str, int words, int chars, int ignore_blanks);
136 char *find_braced_end (char *str);
137 void writelines (char **linearray, int nlines, FILE *ostream);
138 int compare_field (struct keyfield *keyfield, char *start1,
139                    long int length1, long int pos1, char *start2,
140                    long int length2, long int pos2);
141 int compare_full (const void *, const void *);
142 long readline (struct linebuffer *linebuffer, FILE *stream);
143 int merge_files (char **infiles, int nfiles, char *outfile);
144 int merge_direct (char **infiles, int nfiles, char *outfile);
145 void pfatal_with_name (const char *name);
146 void fatal (const char *format, const char *arg);
147 void error (const char *format, const char *arg);
148 void *xmalloc (), *xrealloc ();
149 void flush_tempfiles (int to_count);
150 
151 #define MAX_IN_CORE_SORT 500000
152 
153 int
main(int argc,char ** argv)154 main (int argc, char **argv)
155 {
156   int i;
157 
158   tempcount = 0;
159   last_deleted_tempcount = 0;
160 
161 #ifdef HAVE_SETLOCALE
162   /* Set locale via LC_ALL.  */
163   setlocale (LC_ALL, "");
164 #endif
165 
166   /* In case we write to a redirected stdout that fails.  */
167   /* not ready atexit (close_stdout); */
168 
169   /* Describe the kind of sorting to do. */
170   /* The first keyfield uses the first braced field and folds case. */
171   keyfields[0].braced = 1;
172   keyfields[0].fold_case = 1;
173   keyfields[0].endwords = -1;
174   keyfields[0].endchars = -1;
175 
176   /* The second keyfield uses the second braced field, numerically. */
177   keyfields[1].braced = 1;
178   keyfields[1].numeric = 1;
179   keyfields[1].startwords = 1;
180   keyfields[1].endwords = -1;
181   keyfields[1].endchars = -1;
182 
183   /* The third keyfield (which is ignored while discarding duplicates)
184      compares the whole line. */
185   keyfields[2].endwords = -1;
186   keyfields[2].endchars = -1;
187 
188   decode_command (argc, argv);
189 
190   /* Process input files completely, one by one.  */
191 
192   for (i = 0; i < num_infiles; i++)
193     {
194       int desc;
195       off_t ptr;
196       char *outfile;
197       struct stat instat;
198 
199       desc = open (infiles[i], O_RDONLY, 0);
200       if (desc < 0)
201         pfatal_with_name (infiles[i]);
202 
203       if (stat (infiles[i], &instat))
204         pfatal_with_name (infiles[i]);
205       if (S_ISDIR (instat.st_mode))
206         {
207 #ifdef EISDIR
208           errno = EISDIR;
209 #endif
210           pfatal_with_name (infiles[i]);
211         }
212 
213       lseek (desc, (off_t) 0, SEEK_END);
214       ptr = (off_t) lseek (desc, (off_t) 0, SEEK_CUR);
215 
216       close (desc);
217 
218       outfile = outfiles[i];
219       if (!outfile)
220         outfile = concat (infiles[i], "s", NULL);
221 
222       need_initials = 0;
223       first_initial = '\0';
224 
225       if (ptr < MAX_IN_CORE_SORT)
226         /* Sort a small amount of data. */
227         sort_in_core (infiles[i], (int)ptr, outfile);
228       else
229         sort_offline (infiles[i], ptr, outfile);
230     }
231 
232   flush_tempfiles (tempcount);
233   xexit (0);
234   return 0; /* Avoid bogus warnings.  */
235 }
236 
237 typedef struct
238 {
239   char *long_name;
240   char *short_name;
241   int *variable_ref;
242   int variable_value;
243   char *arg_name;
244   char *doc_string;
245 } TEXINDEX_OPTION;
246 
247 TEXINDEX_OPTION texindex_options[] = {
248   { "--help", "-h", (int *)NULL, 0, (char *)NULL,
249       N_("display this help and exit") },
250   { "--keep", "-k", &keep_tempfiles, 1, (char *)NULL,
251       N_("keep temporary files around after processing") },
252   { "--no-keep", 0, &keep_tempfiles, 0, (char *)NULL,
253       N_("do not keep temporary files around after processing (default)") },
254   { "--output", "-o", (int *)NULL, 0, "FILE",
255       N_("send output to FILE") },
256   { "--version", (char *)NULL, (int *)NULL, 0, (char *)NULL,
257       N_("display version information and exit") },
258   { (char *)NULL, (char *)NULL, (int *)NULL, 0, (char *)NULL }
259 };
260 
261 void
usage(int result_value)262 usage (int result_value)
263 {
264   register int i;
265   FILE *f = result_value ? stderr : stdout;
266 
267   fprintf (f, _("Usage: %s [OPTION]... FILE...\n"), program_name);
268   fprintf (f, _("Generate a sorted index for each TeX output FILE.\n"));
269   /* Avoid trigraph nonsense.  */
270   fprintf (f,
271 _("Usually FILE... is specified as `foo.%c%c\' for a document `foo.texi'.\n"),
272            '?', '?'); /* avoid trigraph in cat-id-tbl.c */
273   fprintf (f, _("\nOptions:\n"));
274 
275   for (i = 0; texindex_options[i].long_name; i++)
276     {
277       putc (' ', f);
278 
279       if (texindex_options[i].short_name)
280         fprintf (f, "%s, ", texindex_options[i].short_name);
281 
282       fprintf (f, "%s %s",
283                texindex_options[i].long_name,
284                texindex_options[i].arg_name
285                ? texindex_options[i].arg_name : "");
286 
287       fprintf (f, "\t%s\n", _(texindex_options[i].doc_string));
288     }
289   fputs (_("\n\
290 Email bug reports to bug-texinfo@gnu.org,\n\
291 general questions and discussion to help-texinfo@gnu.org.\n\
292 Texinfo home page: http://www.gnu.org/software/texinfo/"), f);
293   fputs ("\n", f);
294 
295   xexit (result_value);
296 }
297 
298 /* Decode the command line arguments to set the parameter variables
299    and set up the vector of keyfields and the vector of input files. */
300 
301 void
decode_command(int argc,char ** argv)302 decode_command (int argc, char **argv)
303 {
304   int arg_index = 1;
305   char **ip;
306   char **op;
307 
308   /* Store default values into parameter variables. */
309 
310   tempdir = getenv ("TMPDIR");
311   if (tempdir == NULL)
312     tempdir = getenv ("TEMP");
313   if (tempdir == NULL)
314     tempdir = getenv ("TMP");
315   if (tempdir == NULL)
316     tempdir = DEFAULT_TMPDIR;
317   else
318     tempdir = concat (tempdir, "/", NULL);
319 
320   keep_tempfiles = 0;
321 
322   /* Allocate ARGC input files, which must be enough.  */
323 
324   infiles = (char **) xmalloc (argc * sizeof (char *));
325   outfiles = (char **) xmalloc (argc * sizeof (char *));
326   ip = infiles;
327   op = outfiles;
328 
329   while (arg_index < argc)
330     {
331       char *arg = argv[arg_index++];
332 
333       if (*arg == '-')
334         {
335           if (strcmp (arg, "--version") == 0)
336             {
337               printf ("texindex (GNU %s) %s\n", PACKAGE, VERSION);
338               puts ("");
339               puts ("Copyright (C) 2004 Free Software Foundation, Inc.");
340               printf (_("There is NO warranty.  You may redistribute this software\n\
341 under the terms of the GNU General Public License.\n\
342 For more information about these matters, see the files named COPYING.\n"));
343               xexit (0);
344             }
345           else if ((strcmp (arg, "--keep") == 0) ||
346                    (strcmp (arg, "-k") == 0))
347             {
348               keep_tempfiles = 1;
349             }
350           else if ((strcmp (arg, "--help") == 0) ||
351                    (strcmp (arg, "-h") == 0))
352             {
353               usage (0);
354             }
355           else if ((strcmp (arg, "--output") == 0) ||
356                    (strcmp (arg, "-o") == 0))
357             {
358               if (argv[arg_index] != (char *)NULL)
359                 {
360                   arg_index++;
361                   if (op > outfiles)
362                     *(op - 1) = argv[arg_index];
363                 }
364               else
365                 usage (1);
366             }
367           else
368             usage (1);
369         }
370       else
371         {
372           *ip++ = arg;
373           *op++ = (char *)NULL;
374         }
375     }
376 
377   /* Record number of keyfields and terminate list of filenames. */
378   num_infiles = ip - infiles;
379   *ip = (char *)NULL;
380   if (num_infiles == 0)
381     usage (1);
382 }
383 
384 /* Return a name for temporary file COUNT. */
385 
386 static char *
maketempname(int count)387 maketempname (int count)
388 {
389   static char *tempbase = NULL;
390   char tempsuffix[10];
391 
392   if (!tempbase)
393     {
394       int fd;
395       tempbase = concat (tempdir, "txidxXXXXXX", NULL);
396 
397       fd = mkstemp (tempbase);
398       if (fd == -1)
399         pfatal_with_name (tempbase);
400     }
401 
402   snprintf (tempsuffix, 10, ".%d", count);
403   return concat (tempbase, tempsuffix, NULL);
404 }
405 
406 
407 /* Delete all temporary files up to TO_COUNT. */
408 
409 void
flush_tempfiles(int to_count)410 flush_tempfiles (int to_count)
411 {
412   if (keep_tempfiles)
413     return;
414   while (last_deleted_tempcount < to_count)
415     unlink (maketempname (++last_deleted_tempcount));
416 }
417 
418 
419 /* Compare LINE1 and LINE2 according to the specified set of keyfields. */
420 
421 int
compare_full(const void * p1,const void * p2)422 compare_full (const void *p1, const void *p2)
423 {
424   char **line1 = (char **) p1;
425   char **line2 = (char **) p2;
426   int i;
427 
428   /* Compare using the first keyfield;
429      if that does not distinguish the lines, try the second keyfield;
430      and so on. */
431 
432   for (i = 0; i < num_keyfields; i++)
433     {
434       long length1, length2;
435       char *start1 = find_field (&keyfields[i], *line1, &length1);
436       char *start2 = find_field (&keyfields[i], *line2, &length2);
437       int tem = compare_field (&keyfields[i], start1, length1,
438                                *line1 - text_base,
439                                start2, length2, *line2 - text_base);
440       if (tem)
441         {
442           if (keyfields[i].reverse)
443             return -tem;
444           return tem;
445         }
446     }
447 
448   return 0;                     /* Lines match exactly. */
449 }
450 
451 /* Compare LINE1 and LINE2, described by structures
452    in which the first keyfield is identified in advance.
453    For positional sorting, assumes that the order of the lines in core
454    reflects their nominal order.  */
455 int
compare_prepared(const void * p1,const void * p2)456 compare_prepared (const void *p1, const void *p2)
457 {
458   struct lineinfo *line1 = (struct lineinfo *) p1;
459   struct lineinfo *line2 = (struct lineinfo *) p2;
460   int i;
461   int tem;
462   char *text1, *text2;
463 
464   /* Compare using the first keyfield, which has been found for us already. */
465   if (keyfields->positional)
466     {
467       if (line1->text - text_base > line2->text - text_base)
468         tem = 1;
469       else
470         tem = -1;
471     }
472   else if (keyfields->numeric)
473     tem = line1->key.number - line2->key.number;
474   else
475     tem = compare_field (keyfields, line1->key.text, line1->keylen, 0,
476                          line2->key.text, line2->keylen, 0);
477   if (tem)
478     {
479       if (keyfields->reverse)
480         return -tem;
481       return tem;
482     }
483 
484   text1 = line1->text;
485   text2 = line2->text;
486 
487   /* Compare using the second keyfield;
488      if that does not distinguish the lines, try the third keyfield;
489      and so on. */
490 
491   for (i = 1; i < num_keyfields; i++)
492     {
493       long length1, length2;
494       char *start1 = find_field (&keyfields[i], text1, &length1);
495       char *start2 = find_field (&keyfields[i], text2, &length2);
496       int tem = compare_field (&keyfields[i], start1, length1,
497                                text1 - text_base,
498                                start2, length2, text2 - text_base);
499       if (tem)
500         {
501           if (keyfields[i].reverse)
502             return -tem;
503           return tem;
504         }
505     }
506 
507   return 0;                     /* Lines match exactly. */
508 }
509 
510 /* Like compare_full but more general.
511    You can pass any strings, and you can say how many keyfields to use.
512    POS1 and POS2 should indicate the nominal positional ordering of
513    the two lines in the input.  */
514 
515 int
compare_general(char * str1,char * str2,long int pos1,long int pos2,int use_keyfields)516 compare_general (char *str1, char *str2, long int pos1, long int pos2, int use_keyfields)
517 {
518   int i;
519 
520   /* Compare using the first keyfield;
521      if that does not distinguish the lines, try the second keyfield;
522      and so on. */
523 
524   for (i = 0; i < use_keyfields; i++)
525     {
526       long length1, length2;
527       char *start1 = find_field (&keyfields[i], str1, &length1);
528       char *start2 = find_field (&keyfields[i], str2, &length2);
529       int tem = compare_field (&keyfields[i], start1, length1, pos1,
530                                start2, length2, pos2);
531       if (tem)
532         {
533           if (keyfields[i].reverse)
534             return -tem;
535           return tem;
536         }
537     }
538 
539   return 0;                     /* Lines match exactly. */
540 }
541 
542 /* Find the start and length of a field in STR according to KEYFIELD.
543    A pointer to the starting character is returned, and the length
544    is stored into the int that LENGTHPTR points to.  */
545 
546 char *
find_field(struct keyfield * keyfield,char * str,long int * lengthptr)547 find_field (struct keyfield *keyfield, char *str, long int *lengthptr)
548 {
549   char *start;
550   char *end;
551   char *(*fun) ();
552 
553   if (keyfield->braced)
554     fun = find_braced_pos;
555   else
556     fun = find_pos;
557 
558   start = (*fun) (str, keyfield->startwords, keyfield->startchars,
559                   keyfield->ignore_blanks);
560   if (keyfield->endwords < 0)
561     {
562       if (keyfield->braced)
563         end = find_braced_end (start);
564       else
565         {
566           end = start;
567           while (*end && *end != '\n')
568             end++;
569         }
570     }
571   else
572     {
573       end = (*fun) (str, keyfield->endwords, keyfield->endchars, 0);
574       if (end - str < start - str)
575         end = start;
576     }
577   *lengthptr = end - start;
578   return start;
579 }
580 
581 /* Return a pointer to a specified place within STR,
582    skipping (from the beginning) WORDS words and then CHARS chars.
583    If IGNORE_BLANKS is nonzero, we skip all blanks
584    after finding the specified word.  */
585 
586 char *
find_pos(char * str,int words,int chars,int ignore_blanks)587 find_pos (char *str, int words, int chars, int ignore_blanks)
588 {
589   int i;
590   char *p = str;
591 
592   for (i = 0; i < words; i++)
593     {
594       char c;
595       /* Find next bunch of nonblanks and skip them. */
596       while ((c = *p) == ' ' || c == '\t')
597         p++;
598       while ((c = *p) && c != '\n' && !(c == ' ' || c == '\t'))
599         p++;
600       if (!*p || *p == '\n')
601         return p;
602     }
603 
604   while (*p == ' ' || *p == '\t')
605     p++;
606 
607   for (i = 0; i < chars; i++)
608     {
609       if (!*p || *p == '\n')
610         break;
611       p++;
612     }
613   return p;
614 }
615 
616 /* Like find_pos but assumes that each field is surrounded by braces
617    and that braces within fields are balanced. */
618 
619 char *
find_braced_pos(char * str,int words,int chars,int ignore_blanks)620 find_braced_pos (char *str, int words, int chars, int ignore_blanks)
621 {
622   int i;
623   int bracelevel;
624   char *p = str;
625   char c;
626 
627   for (i = 0; i < words; i++)
628     {
629       bracelevel = 1;
630       while ((c = *p++) != '{' && c != '\n' && c)
631         /* Do nothing. */ ;
632       if (c != '{')
633         return p - 1;
634       while (bracelevel)
635         {
636           c = *p++;
637           if (c == '{')
638             bracelevel++;
639           if (c == '}')
640             bracelevel--;
641           if (c == 0 || c == '\n')
642             return p - 1;
643         }
644     }
645 
646   while ((c = *p++) != '{' && c != '\n' && c)
647     /* Do nothing. */ ;
648 
649   if (c != '{')
650     return p - 1;
651 
652   if (ignore_blanks)
653     while ((c = *p) == ' ' || c == '\t')
654       p++;
655 
656   for (i = 0; i < chars; i++)
657     {
658       if (!*p || *p == '\n')
659         break;
660       p++;
661     }
662   return p;
663 }
664 
665 /* Find the end of the balanced-brace field which starts at STR.
666    The position returned is just before the closing brace. */
667 
668 char *
find_braced_end(char * str)669 find_braced_end (char *str)
670 {
671   int bracelevel;
672   char *p = str;
673   char c;
674 
675   bracelevel = 1;
676   while (bracelevel)
677     {
678       c = *p++;
679       if (c == '{')
680         bracelevel++;
681       if (c == '}')
682         bracelevel--;
683       if (c == 0 || c == '\n')
684         return p - 1;
685     }
686   return p - 1;
687 }
688 
689 long
find_value(char * start,long int length)690 find_value (char *start, long int length)
691 {
692   while (length != 0L)
693     {
694       if (isdigit (*start))
695         return atol (start);
696       length--;
697       start++;
698     }
699   return 0l;
700 }
701 
702 /* Vector used to translate characters for comparison.
703    This is how we make all alphanumerics follow all else,
704    and ignore case in the first sorting.  */
705 int char_order[256];
706 
707 void
init_char_order(void)708 init_char_order (void)
709 {
710   int i;
711   for (i = 1; i < 256; i++)
712     char_order[i] = i;
713 
714   for (i = '0'; i <= '9'; i++)
715     char_order[i] += 512;
716 
717   for (i = 'a'; i <= 'z'; i++)
718     {
719       char_order[i] = 512 + i;
720       char_order[i + 'A' - 'a'] = 512 + i;
721     }
722 }
723 
724 /* Compare two fields (each specified as a start pointer and a character count)
725    according to KEYFIELD.
726    The sign of the value reports the relation between the fields. */
727 
728 int
compare_field(struct keyfield * keyfield,char * start1,long int length1,long int pos1,char * start2,long int length2,long int pos2)729 compare_field (struct keyfield *keyfield, char *start1, long int length1,
730                long int pos1, char *start2, long int length2, long int pos2)
731 {
732   if (keyfields->positional)
733     {
734       if (pos1 > pos2)
735         return 1;
736       else
737         return -1;
738     }
739   if (keyfield->numeric)
740     {
741       long value = find_value (start1, length1) - find_value (start2, length2);
742       if (value > 0)
743         return 1;
744       if (value < 0)
745         return -1;
746       return 0;
747     }
748   else
749     {
750       char *p1 = start1;
751       char *p2 = start2;
752       char *e1 = start1 + length1;
753       char *e2 = start2 + length2;
754 
755       while (1)
756         {
757           int c1, c2;
758 
759           if (p1 == e1)
760             c1 = 0;
761           else
762             c1 = *p1++;
763           if (p2 == e2)
764             c2 = 0;
765           else
766             c2 = *p2++;
767 
768           if (char_order[c1] != char_order[c2])
769             return char_order[c1] - char_order[c2];
770           if (!c1)
771             break;
772         }
773 
774       /* Strings are equal except possibly for case.  */
775       p1 = start1;
776       p2 = start2;
777       while (1)
778         {
779           int c1, c2;
780 
781           if (p1 == e1)
782             c1 = 0;
783           else
784             c1 = *p1++;
785           if (p2 == e2)
786             c2 = 0;
787           else
788             c2 = *p2++;
789 
790           if (c1 != c2)
791             /* Reverse sign here so upper case comes out last.  */
792             return c2 - c1;
793           if (!c1)
794             break;
795         }
796 
797       return 0;
798     }
799 }
800 
801 /* A `struct linebuffer' is a structure which holds a line of text.
802    `readline' reads a line from a stream into a linebuffer
803    and works regardless of the length of the line.  */
804 
805 struct linebuffer
806 {
807   long size;
808   char *buffer;
809 };
810 
811 /* Initialize LINEBUFFER for use. */
812 
813 void
initbuffer(struct linebuffer * linebuffer)814 initbuffer (struct linebuffer *linebuffer)
815 {
816   linebuffer->size = 200;
817   linebuffer->buffer = (char *) xmalloc (200);
818 }
819 
820 /* Read a line of text from STREAM into LINEBUFFER.
821    Return the length of the line.  */
822 
823 long
readline(struct linebuffer * linebuffer,FILE * stream)824 readline (struct linebuffer *linebuffer, FILE *stream)
825 {
826   char *buffer = linebuffer->buffer;
827   char *p = linebuffer->buffer;
828   char *end = p + linebuffer->size;
829 
830   while (1)
831     {
832       int c = getc (stream);
833       if (p == end)
834         {
835           buffer = (char *) xrealloc (buffer, linebuffer->size *= 2);
836           p += buffer - linebuffer->buffer;
837           end += buffer - linebuffer->buffer;
838           linebuffer->buffer = buffer;
839         }
840       if (c < 0 || c == '\n')
841         {
842           *p = 0;
843           break;
844         }
845       *p++ = c;
846     }
847 
848   return p - buffer;
849 }
850 
851 /* Sort an input file too big to sort in core.  */
852 
853 void
sort_offline(char * infile,off_t total,char * outfile)854 sort_offline (char *infile, off_t total, char *outfile)
855 {
856   /* More than enough. */
857   int ntemps = 2 * (total + MAX_IN_CORE_SORT - 1) / MAX_IN_CORE_SORT;
858   char **tempfiles = (char **) xmalloc (ntemps * sizeof (char *));
859   FILE *istream = fopen (infile, "r");
860   int i;
861   struct linebuffer lb;
862   long linelength;
863   int failure = 0;
864 
865   initbuffer (&lb);
866 
867   /* Read in one line of input data.  */
868 
869   linelength = readline (&lb, istream);
870 
871   if (lb.buffer[0] != '\\' && lb.buffer[0] != '@')
872     {
873       error (_("%s: not a texinfo index file"), infile);
874       return;
875     }
876 
877   /* Split up the input into `ntemps' temporary files, or maybe fewer,
878      and put the new files' names into `tempfiles' */
879 
880   for (i = 0; i < ntemps; i++)
881     {
882       char *outname = maketempname (++tempcount);
883       FILE *ostream = fopen (outname, "w");
884       long tempsize = 0;
885 
886       if (!ostream)
887         pfatal_with_name (outname);
888       tempfiles[i] = outname;
889 
890       /* Copy lines into this temp file as long as it does not make file
891          "too big" or until there are no more lines.  */
892 
893       while (tempsize + linelength + 1 <= MAX_IN_CORE_SORT)
894         {
895           tempsize += linelength + 1;
896           fputs (lb.buffer, ostream);
897           putc ('\n', ostream);
898 
899           /* Read another line of input data.  */
900 
901           linelength = readline (&lb, istream);
902           if (!linelength && feof (istream))
903             break;
904 
905           if (lb.buffer[0] != '\\' && lb.buffer[0] != '@')
906             {
907               error (_("%s: not a texinfo index file"), infile);
908               failure = 1;
909               goto fail;
910             }
911         }
912       fclose (ostream);
913       if (feof (istream))
914         break;
915     }
916 
917   free (lb.buffer);
918 
919 fail:
920   /* Record number of temp files we actually needed.  */
921 
922   ntemps = i;
923 
924   /* Sort each tempfile into another tempfile.
925     Delete the first set of tempfiles and put the names of the second
926     into `tempfiles'. */
927 
928   for (i = 0; i < ntemps; i++)
929     {
930       char *newtemp = maketempname (++tempcount);
931       sort_in_core (tempfiles[i], MAX_IN_CORE_SORT, newtemp);
932       if (!keep_tempfiles)
933         unlink (tempfiles[i]);
934       tempfiles[i] = newtemp;
935     }
936 
937   if (failure)
938     return;
939 
940   /* Merge the tempfiles together and indexify. */
941 
942   merge_files (tempfiles, ntemps, outfile);
943 }
944 
945 /* Sort INFILE, whose size is TOTAL,
946    assuming that is small enough to be done in-core,
947    then indexify it and send the output to OUTFILE (or to stdout).  */
948 
949 void
sort_in_core(char * infile,int total,char * outfile)950 sort_in_core (char *infile, int total, char *outfile)
951 {
952   char **nextline;
953   char *data = (char *) xmalloc (total + 1);
954   char *file_data;
955   long file_size;
956   int i;
957   FILE *ostream = stdout;
958   struct lineinfo *lineinfo;
959 
960   /* Read the contents of the file into the moby array `data'. */
961 
962   int desc = open (infile, O_RDONLY, 0);
963 
964   if (desc < 0)
965     fatal (_("failure reopening %s"), infile);
966   for (file_size = 0;;)
967     {
968       i = read (desc, data + file_size, total - file_size);
969       if (i <= 0)
970         break;
971       file_size += i;
972     }
973   file_data = data;
974   data[file_size] = 0;
975 
976   close (desc);
977 
978   if (file_size > 0 && data[0] != '\\' && data[0] != '@')
979     {
980       error (_("%s: not a texinfo index file"), infile);
981       return;
982     }
983 
984   init_char_order ();
985 
986   /* Sort routines want to know this address. */
987 
988   text_base = data;
989 
990   /* Create the array of pointers to lines, with a default size
991      frequently enough.  */
992 
993   nlines = total / 50;
994   if (!nlines)
995     nlines = 2;
996   linearray = (char **) xmalloc (nlines * sizeof (char *));
997 
998   /* `nextline' points to the next free slot in this array.
999      `nlines' is the allocated size.  */
1000 
1001   nextline = linearray;
1002 
1003   /* Parse the input file's data, and make entries for the lines.  */
1004 
1005   nextline = parsefile (infile, nextline, file_data, file_size);
1006   if (nextline == 0)
1007     {
1008       error (_("%s: not a texinfo index file"), infile);
1009       return;
1010     }
1011 
1012   /* Sort the lines. */
1013 
1014   /* If we have enough space, find the first keyfield of each line in advance.
1015      Make a `struct lineinfo' for each line, which records the keyfield
1016      as well as the line, and sort them.  */
1017 
1018   lineinfo = malloc ((nextline - linearray) * sizeof (struct lineinfo));
1019 
1020   if (lineinfo)
1021     {
1022       struct lineinfo *lp;
1023       char **p;
1024 
1025       for (lp = lineinfo, p = linearray; p != nextline; lp++, p++)
1026         {
1027           lp->text = *p;
1028           lp->key.text = find_field (keyfields, *p, &lp->keylen);
1029           if (keyfields->numeric)
1030             lp->key.number = find_value (lp->key.text, lp->keylen);
1031         }
1032 
1033       qsort (lineinfo, nextline - linearray, sizeof (struct lineinfo),
1034              compare_prepared);
1035 
1036       for (lp = lineinfo, p = linearray; p != nextline; lp++, p++)
1037         *p = lp->text;
1038 
1039       free (lineinfo);
1040     }
1041   else
1042     qsort (linearray, nextline - linearray, sizeof (char *), compare_full);
1043 
1044   /* Open the output file. */
1045 
1046   if (outfile)
1047     {
1048       ostream = fopen (outfile, "w");
1049       if (!ostream)
1050         pfatal_with_name (outfile);
1051     }
1052 
1053   writelines (linearray, nextline - linearray, ostream);
1054   if (outfile)
1055     fclose (ostream);
1056 
1057   free (linearray);
1058   free (data);
1059 }
1060 
1061 /* Parse an input string in core into lines.
1062    DATA is the input string, and SIZE is its length.
1063    Data goes in LINEARRAY starting at NEXTLINE.
1064    The value returned is the first entry in LINEARRAY still unused.
1065    Value 0 means input file contents are invalid.  */
1066 
1067 char **
parsefile(char * filename,char ** nextline,char * data,long int size)1068 parsefile (char *filename, char **nextline, char *data, long int size)
1069 {
1070   char *p, *end;
1071   char **line = nextline;
1072 
1073   p = data;
1074   end = p + size;
1075   *end = 0;
1076 
1077   while (p != end)
1078     {
1079       if (p[0] != '\\' && p[0] != '@')
1080         return 0;
1081 
1082       *line = p;
1083 
1084       /* Find the first letter of the first field of this line.  If it
1085          is different from the first letter of the first field of the
1086          first line, we need initial headers in the output index.  */
1087       while (*p && *p != '{')
1088         p++;
1089       if (p == end)
1090         return 0;
1091       p++;
1092       if (first_initial)
1093         {
1094           if (first_initial != toupper (*p))
1095             need_initials = 1;
1096         }
1097       else
1098         first_initial = toupper (*p);
1099 
1100       while (*p && *p != '\n')
1101         p++;
1102       if (p != end)
1103         p++;
1104 
1105       line++;
1106       if (line == linearray + nlines)
1107         {
1108           char **old = linearray;
1109           linearray = xrealloc (linearray, sizeof (char *) * (nlines *= 4));
1110           line += linearray - old;
1111         }
1112     }
1113 
1114   return line;
1115 }
1116 
1117 /* Indexification is a filter applied to the sorted lines
1118    as they are being written to the output file.
1119    Multiple entries for the same name, with different page numbers,
1120    get combined into a single entry with multiple page numbers.
1121    The first braced field, which is used for sorting, is discarded.
1122    However, its first character is examined, folded to lower case,
1123    and if it is different from that in the previous line fed to us
1124    a \initial line is written with one argument, the new initial.
1125 
1126    If an entry has four braced fields, then the second and third
1127    constitute primary and secondary names.
1128    In this case, each change of primary name
1129    generates a \primary line which contains only the primary name,
1130    and in between these are \secondary lines which contain
1131    just a secondary name and page numbers. */
1132 
1133 /* The last primary name we wrote a \primary entry for.
1134    If only one level of indexing is being done, this is the last name seen. */
1135 char *lastprimary;
1136 /* Length of storage allocated for lastprimary. */
1137 int lastprimarylength;
1138 
1139 /* Similar, for the secondary name. */
1140 char *lastsecondary;
1141 int lastsecondarylength;
1142 
1143 /* Zero if we are not in the middle of writing an entry.
1144    One if we have written the beginning of an entry but have not
1145    yet written any page numbers into it.
1146    Greater than one if we have written the beginning of an entry
1147    plus at least one page number. */
1148 int pending;
1149 
1150 /* The initial (for sorting purposes) of the last primary entry written.
1151    When this changes, a \initial {c} line is written */
1152 
1153 char *lastinitial;
1154 
1155 int lastinitiallength;
1156 
1157 /* When we need a string of length 1 for the value of lastinitial,
1158    store it here.  */
1159 
1160 char lastinitial1[2];
1161 
1162 /* Initialize static storage for writing an index. */
1163 
1164 void
init_index(void)1165 init_index (void)
1166 {
1167   pending = 0;
1168   lastinitial = lastinitial1;
1169   lastinitial1[0] = 0;
1170   lastinitial1[1] = 0;
1171   lastinitiallength = 0;
1172   lastprimarylength = 100;
1173   lastprimary = (char *) xmalloc (lastprimarylength + 1);
1174   memset (lastprimary, '\0', lastprimarylength + 1);
1175   lastsecondarylength = 100;
1176   lastsecondary = (char *) xmalloc (lastsecondarylength + 1);
1177   memset (lastsecondary, '\0', lastsecondarylength + 1);
1178 }
1179 
1180 /* Indexify.  Merge entries for the same name,
1181    insert headers for each initial character, etc.  */
1182 
1183 void
indexify(char * line,FILE * ostream)1184 indexify (char *line, FILE *ostream)
1185 {
1186   char *primary, *secondary, *pagenumber;
1187   int primarylength, secondarylength = 0, pagelength;
1188   int nosecondary;
1189   int initiallength;
1190   char *initial;
1191   char initial1[2];
1192   register char *p;
1193 
1194   /* First, analyze the parts of the entry fed to us this time. */
1195 
1196   p = find_braced_pos (line, 0, 0, 0);
1197   if (*p == '{')
1198     {
1199       initial = p;
1200       /* Get length of inner pair of braces starting at `p',
1201          including that inner pair of braces.  */
1202       initiallength = find_braced_end (p + 1) + 1 - p;
1203     }
1204   else
1205     {
1206       initial = initial1;
1207       initial1[0] = toupper (*p);
1208       initial1[1] = 0;
1209       initiallength = 1;
1210     }
1211 
1212   pagenumber = find_braced_pos (line, 1, 0, 0);
1213   pagelength = find_braced_end (pagenumber) - pagenumber;
1214   if (pagelength == 0)
1215     fatal (_("No page number in %s"), line);
1216 
1217   primary = find_braced_pos (line, 2, 0, 0);
1218   primarylength = find_braced_end (primary) - primary;
1219 
1220   secondary = find_braced_pos (line, 3, 0, 0);
1221   nosecondary = !*secondary;
1222   if (!nosecondary)
1223     secondarylength = find_braced_end (secondary) - secondary;
1224 
1225   /* If the primary is different from before, make a new primary entry. */
1226   if (strncmp (primary, lastprimary, primarylength))
1227     {
1228       /* Close off current secondary entry first, if one is open. */
1229       if (pending)
1230         {
1231           fputs ("}\n", ostream);
1232           pending = 0;
1233         }
1234 
1235       /* If this primary has a different initial, include an entry for
1236          the initial. */
1237       if (need_initials &&
1238           (initiallength != lastinitiallength ||
1239            strncmp (initial, lastinitial, initiallength)))
1240         {
1241           fprintf (ostream, "\\initial {");
1242           fwrite (initial, 1, initiallength, ostream);
1243           fputs ("}\n", ostream);
1244           if (initial == initial1)
1245             {
1246               lastinitial = lastinitial1;
1247               *lastinitial1 = *initial1;
1248             }
1249           else
1250             {
1251               lastinitial = initial;
1252             }
1253           lastinitiallength = initiallength;
1254         }
1255 
1256       /* Make the entry for the primary.  */
1257       if (nosecondary)
1258         fputs ("\\entry {", ostream);
1259       else
1260         fputs ("\\primary {", ostream);
1261       fwrite (primary, primarylength, 1, ostream);
1262       if (nosecondary)
1263         {
1264           fputs ("}{", ostream);
1265           pending = 1;
1266         }
1267       else
1268         fputs ("}\n", ostream);
1269 
1270       /* Record name of most recent primary. */
1271       if (lastprimarylength < primarylength)
1272         {
1273           lastprimarylength = primarylength + 100;
1274           lastprimary = (char *) xrealloc (lastprimary,
1275                                            1 + lastprimarylength);
1276         }
1277       strncpy (lastprimary, primary, primarylength);
1278       lastprimary[primarylength] = 0;
1279 
1280       /* There is no current secondary within this primary, now. */
1281       lastsecondary[0] = 0;
1282     }
1283 
1284   /* Should not have an entry with no subtopic following one with a
1285      subtopic. */
1286 
1287   if (nosecondary && *lastsecondary)
1288     error (_("entry %s follows an entry with a secondary name"), line);
1289 
1290   /* Start a new secondary entry if necessary. */
1291   if (!nosecondary && strncmp (secondary, lastsecondary, secondarylength))
1292     {
1293       if (pending)
1294         {
1295           fputs ("}\n", ostream);
1296           pending = 0;
1297         }
1298 
1299       /* Write the entry for the secondary.  */
1300       fputs ("\\secondary {", ostream);
1301       fwrite (secondary, secondarylength, 1, ostream);
1302       fputs ("}{", ostream);
1303       pending = 1;
1304 
1305       /* Record name of most recent secondary. */
1306       if (lastsecondarylength < secondarylength)
1307         {
1308           lastsecondarylength = secondarylength + 100;
1309           lastsecondary = (char *) xrealloc (lastsecondary,
1310                                              1 + lastsecondarylength);
1311         }
1312       strncpy (lastsecondary, secondary, secondarylength);
1313       lastsecondary[secondarylength] = 0;
1314     }
1315 
1316   /* Here to add one more page number to the current entry. */
1317   if (pending++ != 1)
1318     fputs (", ", ostream);  /* Punctuate first, if this is not the first. */
1319   fwrite (pagenumber, pagelength, 1, ostream);
1320 }
1321 
1322 /* Close out any unfinished output entry. */
1323 
1324 void
finish_index(FILE * ostream)1325 finish_index (FILE *ostream)
1326 {
1327   if (pending)
1328     fputs ("}\n", ostream);
1329   free (lastprimary);
1330   free (lastsecondary);
1331 }
1332 
1333 /* Copy the lines in the sorted order.
1334    Each line is copied out of the input file it was found in. */
1335 
1336 void
writelines(char ** linearray,int nlines,FILE * ostream)1337 writelines (char **linearray, int nlines, FILE *ostream)
1338 {
1339   char **stop_line = linearray + nlines;
1340   char **next_line;
1341 
1342   init_index ();
1343 
1344   /* Output the text of the lines, and free the buffer space. */
1345 
1346   for (next_line = linearray; next_line != stop_line; next_line++)
1347     {
1348       /* If -u was specified, output the line only if distinct from
1349          previous one.  */
1350       if (next_line == linearray
1351       /* Compare previous line with this one, using only the
1352          explicitly specd keyfields. */
1353           || compare_general (*(next_line - 1), *next_line, 0L, 0L,
1354                               num_keyfields - 1))
1355         {
1356           char *p = *next_line;
1357           char c;
1358 
1359           while ((c = *p++) && c != '\n')
1360             /* Do nothing. */ ;
1361           *(p - 1) = 0;
1362           indexify (*next_line, ostream);
1363         }
1364     }
1365 
1366   finish_index (ostream);
1367 }
1368 
1369 /* Assume (and optionally verify) that each input file is sorted;
1370    merge them and output the result.
1371    Returns nonzero if any input file fails to be sorted.
1372 
1373    This is the high-level interface that can handle an unlimited
1374    number of files.  */
1375 
1376 #define MAX_DIRECT_MERGE 10
1377 
1378 int
merge_files(char ** infiles,int nfiles,char * outfile)1379 merge_files (char **infiles, int nfiles, char *outfile)
1380 {
1381   char **tempfiles;
1382   int ntemps;
1383   int i;
1384   int value = 0;
1385   int start_tempcount = tempcount;
1386 
1387   if (nfiles <= MAX_DIRECT_MERGE)
1388     return merge_direct (infiles, nfiles, outfile);
1389 
1390   /* Merge groups of MAX_DIRECT_MERGE input files at a time,
1391      making a temporary file to hold each group's result.  */
1392 
1393   ntemps = (nfiles + MAX_DIRECT_MERGE - 1) / MAX_DIRECT_MERGE;
1394   tempfiles = (char **) xmalloc (ntemps * sizeof (char *));
1395   for (i = 0; i < ntemps; i++)
1396     {
1397       int nf = MAX_DIRECT_MERGE;
1398       if (i + 1 == ntemps)
1399         nf = nfiles - i * MAX_DIRECT_MERGE;
1400       tempfiles[i] = maketempname (++tempcount);
1401       value |= merge_direct (&infiles[i * MAX_DIRECT_MERGE], nf, tempfiles[i]);
1402     }
1403 
1404   /* All temporary files that existed before are no longer needed
1405      since their contents have been merged into our new tempfiles.
1406      So delete them.  */
1407   flush_tempfiles (start_tempcount);
1408 
1409   /* Now merge the temporary files we created.  */
1410 
1411   merge_files (tempfiles, ntemps, outfile);
1412 
1413   free (tempfiles);
1414 
1415   return value;
1416 }
1417 
1418 /* Assume (and optionally verify) that each input file is sorted;
1419    merge them and output the result.
1420    Returns nonzero if any input file fails to be sorted.
1421 
1422    This version of merging will not work if the number of
1423    input files gets too high.  Higher level functions
1424    use it only with a bounded number of input files.  */
1425 
1426 int
merge_direct(char ** infiles,int nfiles,char * outfile)1427 merge_direct (char **infiles, int nfiles, char *outfile)
1428 {
1429   struct linebuffer *lb1, *lb2;
1430   struct linebuffer **thisline, **prevline;
1431   FILE **streams;
1432   int i;
1433   int nleft;
1434   int lossage = 0;
1435   int *file_lossage;
1436   struct linebuffer *prev_out = 0;
1437   FILE *ostream = stdout;
1438 
1439   if (outfile)
1440     {
1441       ostream = fopen (outfile, "w");
1442     }
1443   if (!ostream)
1444     pfatal_with_name (outfile);
1445 
1446   init_index ();
1447 
1448   if (nfiles == 0)
1449     {
1450       if (outfile)
1451         fclose (ostream);
1452       return 0;
1453     }
1454 
1455   /* For each file, make two line buffers.  Also, for each file, there
1456      is an element of `thisline' which points at any time to one of the
1457      file's two buffers, and an element of `prevline' which points to
1458      the other buffer.  `thisline' is supposed to point to the next
1459      available line from the file, while `prevline' holds the last file
1460      line used, which is remembered so that we can verify that the file
1461      is properly sorted. */
1462 
1463   /* lb1 and lb2 contain one buffer each per file. */
1464   lb1 = (struct linebuffer *) xmalloc (nfiles * sizeof (struct linebuffer));
1465   lb2 = (struct linebuffer *) xmalloc (nfiles * sizeof (struct linebuffer));
1466 
1467   /* thisline[i] points to the linebuffer holding the next available
1468      line in file i, or is zero if there are no lines left in that file.  */
1469   thisline = (struct linebuffer **)
1470     xmalloc (nfiles * sizeof (struct linebuffer *));
1471   /* prevline[i] points to the linebuffer holding the last used line
1472      from file i.  This is just for verifying that file i is properly
1473      sorted.  */
1474   prevline = (struct linebuffer **)
1475     xmalloc (nfiles * sizeof (struct linebuffer *));
1476   /* streams[i] holds the input stream for file i.  */
1477   streams = (FILE **) xmalloc (nfiles * sizeof (FILE *));
1478   /* file_lossage[i] is nonzero if we already know file i is not
1479      properly sorted.  */
1480   file_lossage = (int *) xmalloc (nfiles * sizeof (int));
1481 
1482   /* Allocate and initialize all that storage. */
1483 
1484   for (i = 0; i < nfiles; i++)
1485     {
1486       initbuffer (&lb1[i]);
1487       initbuffer (&lb2[i]);
1488       thisline[i] = &lb1[i];
1489       prevline[i] = &lb2[i];
1490       file_lossage[i] = 0;
1491       streams[i] = fopen (infiles[i], "r");
1492       if (!streams[i])
1493         pfatal_with_name (infiles[i]);
1494 
1495       readline (thisline[i], streams[i]);
1496     }
1497 
1498   /* Keep count of number of files not at eof. */
1499   nleft = nfiles;
1500 
1501   while (nleft)
1502     {
1503       struct linebuffer *best = 0;
1504       struct linebuffer *exch;
1505       int bestfile = -1;
1506       int i;
1507 
1508       /* Look at the next avail line of each file; choose the least one.  */
1509 
1510       for (i = 0; i < nfiles; i++)
1511         {
1512           if (thisline[i] &&
1513               (!best ||
1514                0 < compare_general (best->buffer, thisline[i]->buffer,
1515                                  (long) bestfile, (long) i, num_keyfields)))
1516             {
1517               best = thisline[i];
1518               bestfile = i;
1519             }
1520         }
1521 
1522       /* Output that line, unless it matches the previous one and we
1523          don't want duplicates. */
1524 
1525       if (!(prev_out &&
1526             !compare_general (prev_out->buffer,
1527                               best->buffer, 0L, 1L, num_keyfields - 1)))
1528         indexify (best->buffer, ostream);
1529       prev_out = best;
1530 
1531       /* Now make the line the previous of its file, and fetch a new
1532          line from that file.  */
1533 
1534       exch = prevline[bestfile];
1535       prevline[bestfile] = thisline[bestfile];
1536       thisline[bestfile] = exch;
1537 
1538       while (1)
1539         {
1540           /* If the file has no more, mark it empty. */
1541 
1542           if (feof (streams[bestfile]))
1543             {
1544               thisline[bestfile] = 0;
1545               /* Update the number of files still not empty. */
1546               nleft--;
1547               break;
1548             }
1549           readline (thisline[bestfile], streams[bestfile]);
1550           if (thisline[bestfile]->buffer[0] || !feof (streams[bestfile]))
1551             break;
1552         }
1553     }
1554 
1555   finish_index (ostream);
1556 
1557   /* Free all storage and close all input streams. */
1558 
1559   for (i = 0; i < nfiles; i++)
1560     {
1561       fclose (streams[i]);
1562       free (lb1[i].buffer);
1563       free (lb2[i].buffer);
1564     }
1565   free (file_lossage);
1566   free (lb1);
1567   free (lb2);
1568   free (thisline);
1569   free (prevline);
1570   free (streams);
1571 
1572   if (outfile)
1573     fclose (ostream);
1574 
1575   return lossage;
1576 }
1577 
1578 /* Print error message and exit.  */
1579 
1580 void
fatal(const char * format,const char * arg)1581 fatal (const char *format, const char *arg)
1582 {
1583   error (format, arg);
1584   xexit (1);
1585 }
1586 
1587 /* Print error message.  FORMAT is printf control string, ARG is arg for it. */
1588 void
error(const char * format,const char * arg)1589 error (const char *format, const char *arg)
1590 {
1591   printf ("%s: ", program_name);
1592   printf (format, arg);
1593   if (format[strlen (format) -1] != '\n')
1594     printf ("\n");
1595 }
1596 
1597 void
perror_with_name(const char * name)1598 perror_with_name (const char *name)
1599 {
1600   fprintf (stderr, "%s: ", program_name);
1601   perror (name);
1602 }
1603 
1604 void
pfatal_with_name(const char * name)1605 pfatal_with_name (const char *name)
1606 {
1607   perror_with_name (name);
1608   xexit (1);
1609 }
1610