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