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