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