xref: /openbsd-src/gnu/usr.bin/texinfo/util/texindex.c (revision 8500990981f885cbe5e6a4958549cacc238b5ae6)
1 /* Process TeX index dribble output into an actual index.
2    $Id: texindex.c,v 1.4 2002/06/10 13:51:04 espie Exp $
3 
4    Copyright (C) 1987, 91, 92, 96, 97, 98, 99, 2000, 01, 02
5    Free Software Foundation, Inc.
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2, or (at your option)
10    any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307. */
20 
21 #include "system.h"
22 #include <getopt.h>
23 
24 static char *program_name = "texindex";
25 
26 #if defined (emacs)
27 #  include "../src/config.h"
28 /* Some s/os.h files redefine these. */
29 #  undef read
30 #  undef close
31 #  undef write
32 #  undef open
33 #endif
34 
35 #if !defined (HAVE_MEMSET)
36 #undef memset
37 #define memset(ptr, ignore, count) bzero (ptr, count)
38 #endif
39 
40 char *mktemp ();
41 
42 #if !defined (SEEK_SET)
43 #  define SEEK_SET 0
44 #  define SEEK_CUR 1
45 #  define SEEK_END 2
46 #endif /* !SEEK_SET */
47 
48 /* When sorting in core, this structure describes one line
49    and the position and length of its first keyfield.  */
50 struct lineinfo
51 {
52   char *text;           /* The actual text of the line. */
53   union {
54     char *text;         /* The start of the key (for textual comparison). */
55     long number;        /* The numeric value (for numeric comparison). */
56   } key;
57   long keylen;          /* Length of KEY field. */
58 };
59 
60 /* This structure describes a field to use as a sort key. */
61 struct keyfield
62 {
63   int startwords;       /* Number of words to skip. */
64   int startchars;       /* Number of additional chars to skip. */
65   int endwords;         /* Number of words to ignore at end. */
66   int endchars;         /* Ditto for characters of last word. */
67   char ignore_blanks;   /* Non-zero means ignore spaces and tabs. */
68   char fold_case;       /* Non-zero means case doesn't matter. */
69   char reverse;         /* Non-zero means compare in reverse order. */
70   char numeric;         /* Non-zeros means field is ASCII numeric. */
71   char positional;      /* Sort according to file position. */
72   char braced;          /* Count balanced-braced groupings as fields. */
73 };
74 
75 /* Vector of keyfields to use. */
76 struct keyfield keyfields[3];
77 
78 /* Number of keyfields stored in that vector.  */
79 int num_keyfields = 3;
80 
81 /* Vector of input file names, terminated with a null pointer. */
82 char **infiles;
83 
84 /* Vector of corresponding output file names, or NULL, meaning default it
85    (add an `s' to the end). */
86 char **outfiles;
87 
88 /* Length of `infiles'. */
89 int num_infiles;
90 
91 /* Pointer to the array of pointers to lines being sorted. */
92 char **linearray;
93 
94 /* The allocated length of `linearray'. */
95 long nlines;
96 
97 /* Directory to use for temporary files.  On Unix, it ends with a slash.  */
98 char *tempdir;
99 
100 /* Start of filename to use for temporary files.  */
101 char *tempbase;
102 
103 /* Number of last temporary file.  */
104 int tempcount;
105 
106 /* Number of last temporary file already deleted.
107    Temporary files are deleted by `flush_tempfiles' in order of creation.  */
108 int last_deleted_tempcount;
109 
110 /* During in-core sort, this points to the base of the data block
111    which contains all the lines of data.  */
112 char *text_base;
113 
114 /* Additional command switches .*/
115 
116 /* Nonzero means do not delete tempfiles -- for debugging. */
117 int keep_tempfiles;
118 
119 /* Forward declarations of functions in this file. */
120 void decode_command ();
121 void sort_in_core ();
122 void sort_offline ();
123 char **parsefile ();
124 char *find_field ();
125 char *find_pos ();
126 long find_value ();
127 char *find_braced_pos ();
128 char *find_braced_end ();
129 void writelines ();
130 int compare_field ();
131 int compare_full ();
132 long readline ();
133 int merge_files ();
134 int merge_direct ();
135 void pfatal_with_name ();
136 void fatal ();
137 void error ();
138 void *xmalloc (), *xrealloc ();
139 char *concat ();
140 void flush_tempfiles ();
141 
142 #define MAX_IN_CORE_SORT 500000
143 
144 int
145 main (argc, argv)
146      int argc;
147      char **argv;
148 {
149   int i;
150 
151   tempcount = 0;
152   last_deleted_tempcount = 0;
153 
154 #ifdef HAVE_SETLOCALE
155   /* Set locale via LC_ALL.  */
156   setlocale (LC_ALL, "");
157 #endif
158 
159   /* Set the text message domain.  */
160   bindtextdomain (PACKAGE, LOCALEDIR);
161   textdomain (PACKAGE);
162 
163   /* Describe the kind of sorting to do. */
164   /* The first keyfield uses the first braced field and folds case. */
165   keyfields[0].braced = 1;
166   keyfields[0].fold_case = 1;
167   keyfields[0].endwords = -1;
168   keyfields[0].endchars = -1;
169 
170   /* The second keyfield uses the second braced field, numerically. */
171   keyfields[1].braced = 1;
172   keyfields[1].numeric = 1;
173   keyfields[1].startwords = 1;
174   keyfields[1].endwords = -1;
175   keyfields[1].endchars = -1;
176 
177   /* The third keyfield (which is ignored while discarding duplicates)
178      compares the whole line. */
179   keyfields[2].endwords = -1;
180   keyfields[2].endchars = -1;
181 
182   decode_command (argc, argv);
183 
184   /* XXX mkstemp not appropriate, as we need to have somewhat predictable
185    * names. But race condition was fixed, see maketempname.
186    */
187   tempbase = mktemp (concat ("txiXXXXXX", "", ""));
188 
189   /* Process input files completely, one by one.  */
190 
191   for (i = 0; i < num_infiles; i++)
192     {
193       int desc;
194       off_t ptr;
195       char *outfile;
196       struct stat instat;
197 
198       desc = open (infiles[i], O_RDONLY, 0);
199       if (desc < 0)
200         pfatal_with_name (infiles[i]);
201 
202       if (stat (infiles[i], &instat))
203         pfatal_with_name (infiles[i]);
204       if (S_ISDIR (instat.st_mode))
205         {
206 #ifdef EISDIR
207           errno = EISDIR;
208 #endif
209           pfatal_with_name (infiles[i]);
210         }
211 
212       lseek (desc, (off_t) 0, SEEK_END);
213       ptr = (off_t) lseek (desc, (off_t) 0, SEEK_CUR);
214 
215       close (desc);
216 
217       outfile = outfiles[i];
218       if (!outfile)
219         {
220           outfile = concat (infiles[i], "s", "");
221         }
222 
223       if (ptr < MAX_IN_CORE_SORT)
224         /* Sort a small amount of data. */
225         sort_in_core (infiles[i], ptr, outfile);
226       else
227         sort_offline (infiles[i], ptr, outfile);
228     }
229 
230   flush_tempfiles (tempcount);
231   xexit (0);
232 
233   return 0; /* Avoid bogus warnings.  */
234 }
235 
236 typedef struct
237 {
238   char *long_name;
239   char *short_name;
240   int *variable_ref;
241   int variable_value;
242   char *arg_name;
243   char *doc_string;
244 } TEXINDEX_OPTION;
245 
246 TEXINDEX_OPTION texindex_options[] = {
247   { "--help", "-h", (int *)NULL, 0, (char *)NULL,
248       N_("display this help and exit") },
249   { "--keep", "-k", &keep_tempfiles, 1, (char *)NULL,
250       N_("keep temporary files around after processing") },
251   { "--no-keep", 0, &keep_tempfiles, 0, (char *)NULL,
252       N_("do not keep temporary files around after processing (default)") },
253   { "--output", "-o", (int *)NULL, 0, "FILE",
254       N_("send output to FILE") },
255   { "--version", (char *)NULL, (int *)NULL, 0, (char *)NULL,
256       N_("display version information and exit") },
257   { (char *)NULL, (char *)NULL, (int *)NULL, 0, (char *)NULL }
258 };
259 
260 void
261 usage (result_value)
262      int result_value;
263 {
264   register int i;
265   FILE *f = result_value ? stderr : stdout;
266 
267   fprintf (f, _("Usage: %s [OPTION]... FILE...\n"), program_name);
268   fprintf (f, _("Generate a sorted index for each TeX output FILE.\n"));
269   /* Avoid trigraph nonsense.  */
270   fprintf (f,
271 _("Usually FILE... is specified as `foo.%c%c\' for a document `foo.texi'.\n"),
272            '?', '?'); /* avoid trigraph in cat-id-tbl.c */
273   fprintf (f, _("\nOptions:\n"));
274 
275   for (i = 0; texindex_options[i].long_name; i++)
276     {
277       putc (' ', f);
278 
279       if (texindex_options[i].short_name)
280         fprintf (f, "%s, ", texindex_options[i].short_name);
281 
282       fprintf (f, "%s %s",
283                texindex_options[i].long_name,
284                texindex_options[i].arg_name
285                ? texindex_options[i].arg_name : "");
286 
287       fprintf (f, "\t%s\n", _(texindex_options[i].doc_string));
288     }
289   fputs (_("\n\
290 Email bug reports to bug-texinfo@gnu.org,\n\
291 general questions and discussion to help-texinfo@gnu.org.\n\
292 Texinfo home page: http://www.gnu.org/software/texinfo/"), f);
293   fputs ("\n", f);
294 
295   xexit (result_value);
296 }
297 
298 /* Decode the command line arguments to set the parameter variables
299    and set up the vector of keyfields and the vector of input files. */
300 
301 void
302 decode_command (argc, argv)
303      int argc;
304      char **argv;
305 {
306   int arg_index = 1;
307   char **ip;
308   char **op;
309 
310   /* Store default values into parameter variables. */
311 
312   tempdir = getenv ("TMPDIR");
313   if (tempdir == NULL)
314     tempdir = getenv ("TEMP");
315   if (tempdir == NULL)
316     tempdir = getenv ("TMP");
317   if (tempdir == NULL)
318     tempdir = DEFAULT_TMPDIR;
319   else
320     tempdir = concat (tempdir, "/", "");
321 
322   keep_tempfiles = 0;
323 
324   /* Allocate ARGC input files, which must be enough.  */
325 
326   infiles = (char **) xmalloc (argc * sizeof (char *));
327   outfiles = (char **) xmalloc (argc * sizeof (char *));
328   ip = infiles;
329   op = outfiles;
330 
331   while (arg_index < argc)
332     {
333       char *arg = argv[arg_index++];
334 
335       if (*arg == '-')
336         {
337           if (strcmp (arg, "--version") == 0)
338             {
339               printf ("texindex (GNU %s) %s\n", PACKAGE, VERSION);
340               puts ("");
341               printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
342 There is NO warranty.  You may redistribute this software\n\
343 under the terms of the GNU General Public License.\n\
344 For more information about these matters, see the files named COPYING.\n"),
345                   "2002");
346               xexit (0);
347             }
348           else if ((strcmp (arg, "--keep") == 0) ||
349                    (strcmp (arg, "-k") == 0))
350             {
351               keep_tempfiles = 1;
352             }
353           else if ((strcmp (arg, "--help") == 0) ||
354                    (strcmp (arg, "-h") == 0))
355             {
356               usage (0);
357             }
358           else if ((strcmp (arg, "--output") == 0) ||
359                    (strcmp (arg, "-o") == 0))
360             {
361               if (argv[arg_index] != (char *)NULL)
362                 {
363                   arg_index++;
364                   if (op > outfiles)
365                     *(op - 1) = argv[arg_index];
366                 }
367               else
368                 usage (1);
369             }
370           else
371             usage (1);
372         }
373       else
374         {
375           *ip++ = arg;
376           *op++ = (char *)NULL;
377         }
378     }
379 
380   /* Record number of keyfields and terminate list of filenames. */
381   num_infiles = ip - infiles;
382   *ip = (char *)NULL;
383   if (num_infiles == 0)
384     usage (1);
385 }
386 
387 /* Return a name for a temporary file. */
388 
389 static char *
390 maketempname (count)
391      int count;
392 {
393   char tempsuffix[10];
394   char *name;
395   int fd;
396 
397   sprintf (tempsuffix, ".%d", count);
398   name =  concat (tempdir, tempbase, tempsuffix);
399 
400   fd = open (name, O_CREAT|O_EXCL|O_WRONLY, 0666);
401   if (fd == -1)
402     return NULL;
403   else
404     {
405       close(fd);
406       return name;
407     }
408 }
409 
410 /* Delete all temporary files up to TO_COUNT. */
411 
412 void
413 flush_tempfiles (to_count)
414      int to_count;
415 {
416   if (keep_tempfiles)
417     return;
418   while (last_deleted_tempcount < to_count)
419     unlink (maketempname (++last_deleted_tempcount));
420 }
421 
422 
423 /* Compare LINE1 and LINE2 according to the specified set of keyfields. */
424 
425 int
426 compare_full (line1, line2)
427      char **line1, **line2;
428 {
429   int i;
430 
431   /* Compare using the first keyfield;
432      if that does not distinguish the lines, try the second keyfield;
433      and so on. */
434 
435   for (i = 0; i < num_keyfields; i++)
436     {
437       long length1, length2;
438       char *start1 = find_field (&keyfields[i], *line1, &length1);
439       char *start2 = find_field (&keyfields[i], *line2, &length2);
440       int tem = compare_field (&keyfields[i], start1, length1, *line1 - text_base,
441                                start2, length2, *line2 - text_base);
442       if (tem)
443         {
444           if (keyfields[i].reverse)
445             return -tem;
446           return tem;
447         }
448     }
449 
450   return 0;                     /* Lines match exactly. */
451 }
452 
453 /* Compare LINE1 and LINE2, described by structures
454    in which the first keyfield is identified in advance.
455    For positional sorting, assumes that the order of the lines in core
456    reflects their nominal order.  */
457 
458 int
459 compare_prepared (line1, line2)
460      struct lineinfo *line1, *line2;
461 {
462   int i;
463   int tem;
464   char *text1, *text2;
465 
466   /* Compare using the first keyfield, which has been found for us already. */
467   if (keyfields->positional)
468     {
469       if (line1->text - text_base > line2->text - text_base)
470         tem = 1;
471       else
472         tem = -1;
473     }
474   else if (keyfields->numeric)
475     tem = line1->key.number - line2->key.number;
476   else
477     tem = compare_field (keyfields, line1->key.text, line1->keylen, 0,
478                          line2->key.text, line2->keylen, 0);
479   if (tem)
480     {
481       if (keyfields->reverse)
482         return -tem;
483       return tem;
484     }
485 
486   text1 = line1->text;
487   text2 = line2->text;
488 
489   /* Compare using the second keyfield;
490      if that does not distinguish the lines, try the third keyfield;
491      and so on. */
492 
493   for (i = 1; i < num_keyfields; i++)
494     {
495       long length1, length2;
496       char *start1 = find_field (&keyfields[i], text1, &length1);
497       char *start2 = find_field (&keyfields[i], text2, &length2);
498       int tem = compare_field (&keyfields[i], start1, length1, text1 - text_base,
499                                start2, length2, text2 - text_base);
500       if (tem)
501         {
502           if (keyfields[i].reverse)
503             return -tem;
504           return tem;
505         }
506     }
507 
508   return 0;                     /* Lines match exactly. */
509 }
510 
511 /* Like compare_full but more general.
512    You can pass any strings, and you can say how many keyfields to use.
513    POS1 and POS2 should indicate the nominal positional ordering of
514    the two lines in the input.  */
515 
516 int
517 compare_general (str1, str2, pos1, pos2, use_keyfields)
518      char *str1, *str2;
519      long pos1, pos2;
520      int use_keyfields;
521 {
522   int i;
523 
524   /* Compare using the first keyfield;
525      if that does not distinguish the lines, try the second keyfield;
526      and so on. */
527 
528   for (i = 0; i < use_keyfields; i++)
529     {
530       long length1, length2;
531       char *start1 = find_field (&keyfields[i], str1, &length1);
532       char *start2 = find_field (&keyfields[i], str2, &length2);
533       int tem = compare_field (&keyfields[i], start1, length1, pos1,
534                                start2, length2, pos2);
535       if (tem)
536         {
537           if (keyfields[i].reverse)
538             return -tem;
539           return tem;
540         }
541     }
542 
543   return 0;                     /* Lines match exactly. */
544 }
545 
546 /* Find the start and length of a field in STR according to KEYFIELD.
547    A pointer to the starting character is returned, and the length
548    is stored into the int that LENGTHPTR points to.  */
549 
550 char *
551 find_field (keyfield, str, lengthptr)
552      struct keyfield *keyfield;
553      char *str;
554      long *lengthptr;
555 {
556   char *start;
557   char *end;
558   char *(*fun) ();
559 
560   if (keyfield->braced)
561     fun = find_braced_pos;
562   else
563     fun = find_pos;
564 
565   start = (*fun) (str, keyfield->startwords, keyfield->startchars,
566                   keyfield->ignore_blanks);
567   if (keyfield->endwords < 0)
568     {
569       if (keyfield->braced)
570         end = find_braced_end (start);
571       else
572         {
573           end = start;
574           while (*end && *end != '\n')
575             end++;
576         }
577     }
578   else
579     {
580       end = (*fun) (str, keyfield->endwords, keyfield->endchars, 0);
581       if (end - str < start - str)
582         end = start;
583     }
584   *lengthptr = end - start;
585   return start;
586 }
587 
588 /* Return a pointer to a specified place within STR,
589    skipping (from the beginning) WORDS words and then CHARS chars.
590    If IGNORE_BLANKS is nonzero, we skip all blanks
591    after finding the specified word.  */
592 
593 char *
594 find_pos (str, words, chars, ignore_blanks)
595      char *str;
596      int words, chars;
597      int ignore_blanks;
598 {
599   int i;
600   char *p = str;
601 
602   for (i = 0; i < words; i++)
603     {
604       char c;
605       /* Find next bunch of nonblanks and skip them. */
606       while ((c = *p) == ' ' || c == '\t')
607         p++;
608       while ((c = *p) && c != '\n' && !(c == ' ' || c == '\t'))
609         p++;
610       if (!*p || *p == '\n')
611         return p;
612     }
613 
614   while (*p == ' ' || *p == '\t')
615     p++;
616 
617   for (i = 0; i < chars; i++)
618     {
619       if (!*p || *p == '\n')
620         break;
621       p++;
622     }
623   return p;
624 }
625 
626 /* Like find_pos but assumes that each field is surrounded by braces
627    and that braces within fields are balanced. */
628 
629 char *
630 find_braced_pos (str, words, chars, ignore_blanks)
631      char *str;
632      int words, chars;
633      int ignore_blanks;
634 {
635   int i;
636   int bracelevel;
637   char *p = str;
638   char c;
639 
640   for (i = 0; i < words; i++)
641     {
642       bracelevel = 1;
643       while ((c = *p++) != '{' && c != '\n' && c)
644         /* Do nothing. */ ;
645       if (c != '{')
646         return p - 1;
647       while (bracelevel)
648         {
649           c = *p++;
650           if (c == '{')
651             bracelevel++;
652           if (c == '}')
653             bracelevel--;
654           if (c == 0 || c == '\n')
655             return p - 1;
656         }
657     }
658 
659   while ((c = *p++) != '{' && c != '\n' && c)
660     /* Do nothing. */ ;
661 
662   if (c != '{')
663     return p - 1;
664 
665   if (ignore_blanks)
666     while ((c = *p) == ' ' || c == '\t')
667       p++;
668 
669   for (i = 0; i < chars; i++)
670     {
671       if (!*p || *p == '\n')
672         break;
673       p++;
674     }
675   return p;
676 }
677 
678 /* Find the end of the balanced-brace field which starts at STR.
679    The position returned is just before the closing brace. */
680 
681 char *
682 find_braced_end (str)
683      char *str;
684 {
685   int bracelevel;
686   char *p = str;
687   char c;
688 
689   bracelevel = 1;
690   while (bracelevel)
691     {
692       c = *p++;
693       if (c == '{')
694         bracelevel++;
695       if (c == '}')
696         bracelevel--;
697       if (c == 0 || c == '\n')
698         return p - 1;
699     }
700   return p - 1;
701 }
702 
703 long
704 find_value (start, length)
705      char *start;
706      long length;
707 {
708   while (length != 0L)
709     {
710       if (isdigit (*start))
711         return atol (start);
712       length--;
713       start++;
714     }
715   return 0l;
716 }
717 
718 /* Vector used to translate characters for comparison.
719    This is how we make all alphanumerics follow all else,
720    and ignore case in the first sorting.  */
721 int char_order[256];
722 
723 void
724 init_char_order ()
725 {
726   int i;
727   for (i = 1; i < 256; i++)
728     char_order[i] = i;
729 
730   for (i = '0'; i <= '9'; i++)
731     char_order[i] += 512;
732 
733   for (i = 'a'; i <= 'z'; i++)
734     {
735       char_order[i] = 512 + i;
736       char_order[i + 'A' - 'a'] = 512 + i;
737     }
738 }
739 
740 /* Compare two fields (each specified as a start pointer and a character count)
741    according to KEYFIELD.
742    The sign of the value reports the relation between the fields. */
743 
744 int
745 compare_field (keyfield, start1, length1, pos1, start2, length2, pos2)
746      struct keyfield *keyfield;
747      char *start1;
748      long length1;
749      long pos1;
750      char *start2;
751      long length2;
752      long pos2;
753 {
754   if (keyfields->positional)
755     {
756       if (pos1 > pos2)
757         return 1;
758       else
759         return -1;
760     }
761   if (keyfield->numeric)
762     {
763       long value = find_value (start1, length1) - find_value (start2, length2);
764       if (value > 0)
765         return 1;
766       if (value < 0)
767         return -1;
768       return 0;
769     }
770   else
771     {
772       char *p1 = start1;
773       char *p2 = start2;
774       char *e1 = start1 + length1;
775       char *e2 = start2 + length2;
776 
777       while (1)
778         {
779           int c1, c2;
780 
781           if (p1 == e1)
782             c1 = 0;
783           else
784             c1 = *p1++;
785           if (p2 == e2)
786             c2 = 0;
787           else
788             c2 = *p2++;
789 
790           if (char_order[c1] != char_order[c2])
791             return char_order[c1] - char_order[c2];
792           if (!c1)
793             break;
794         }
795 
796       /* Strings are equal except possibly for case.  */
797       p1 = start1;
798       p2 = start2;
799       while (1)
800         {
801           int c1, c2;
802 
803           if (p1 == e1)
804             c1 = 0;
805           else
806             c1 = *p1++;
807           if (p2 == e2)
808             c2 = 0;
809           else
810             c2 = *p2++;
811 
812           if (c1 != c2)
813             /* Reverse sign here so upper case comes out last.  */
814             return c2 - c1;
815           if (!c1)
816             break;
817         }
818 
819       return 0;
820     }
821 }
822 
823 /* A `struct linebuffer' is a structure which holds a line of text.
824    `readline' reads a line from a stream into a linebuffer
825    and works regardless of the length of the line.  */
826 
827 struct linebuffer
828 {
829   long size;
830   char *buffer;
831 };
832 
833 /* Initialize LINEBUFFER for use. */
834 
835 void
836 initbuffer (linebuffer)
837      struct linebuffer *linebuffer;
838 {
839   linebuffer->size = 200;
840   linebuffer->buffer = (char *) xmalloc (200);
841 }
842 
843 /* Read a line of text from STREAM into LINEBUFFER.
844    Return the length of the line.  */
845 
846 long
847 readline (linebuffer, stream)
848      struct linebuffer *linebuffer;
849      FILE *stream;
850 {
851   char *buffer = linebuffer->buffer;
852   char *p = linebuffer->buffer;
853   char *end = p + linebuffer->size;
854 
855   while (1)
856     {
857       int c = getc (stream);
858       if (p == end)
859         {
860           buffer = (char *) xrealloc (buffer, linebuffer->size *= 2);
861           p += buffer - linebuffer->buffer;
862           end += buffer - linebuffer->buffer;
863           linebuffer->buffer = buffer;
864         }
865       if (c < 0 || c == '\n')
866         {
867           *p = 0;
868           break;
869         }
870       *p++ = c;
871     }
872 
873   return p - buffer;
874 }
875 
876 /* Sort an input file too big to sort in core.  */
877 
878 void
879 sort_offline (infile, nfiles, total, outfile)
880      char *infile;
881      int nfiles;
882      off_t total;
883      char *outfile;
884 {
885   /* More than enough. */
886   int ntemps = 2 * (total + MAX_IN_CORE_SORT - 1) / MAX_IN_CORE_SORT;
887   char **tempfiles = (char **) xmalloc (ntemps * sizeof (char *));
888   FILE *istream = fopen (infile, "r");
889   int i;
890   struct linebuffer lb;
891   long linelength;
892   int failure = 0;
893 
894   initbuffer (&lb);
895 
896   /* Read in one line of input data.  */
897 
898   linelength = readline (&lb, istream);
899 
900   if (lb.buffer[0] != '\\' && lb.buffer[0] != '@')
901     {
902       error (_("%s: not a texinfo index file"), infile);
903       return;
904     }
905 
906   /* Split up the input into `ntemps' temporary files, or maybe fewer,
907      and put the new files' names into `tempfiles' */
908 
909   for (i = 0; i < ntemps; i++)
910     {
911       char *outname = maketempname (++tempcount);
912       FILE *ostream;
913       long tempsize = 0;
914 
915       if (!outname)
916         pfatal_with_name("temporary file");
917       ostream = fopen (outname, "w");
918       if (!outname || !ostream)
919         pfatal_with_name (outname);
920       tempfiles[i] = outname;
921 
922       /* Copy lines into this temp file as long as it does not make file
923          "too big" or until there are no more lines.  */
924 
925       while (tempsize + linelength + 1 <= MAX_IN_CORE_SORT)
926         {
927           tempsize += linelength + 1;
928           fputs (lb.buffer, ostream);
929           putc ('\n', ostream);
930 
931           /* Read another line of input data.  */
932 
933           linelength = readline (&lb, istream);
934           if (!linelength && feof (istream))
935             break;
936 
937           if (lb.buffer[0] != '\\' && lb.buffer[0] != '@')
938             {
939               error (_("%s: not a texinfo index file"), infile);
940               failure = 1;
941               goto fail;
942             }
943         }
944       fclose (ostream);
945       if (feof (istream))
946         break;
947     }
948 
949   free (lb.buffer);
950 
951 fail:
952   /* Record number of temp files we actually needed.  */
953 
954   ntemps = i;
955 
956   /* Sort each tempfile into another tempfile.
957     Delete the first set of tempfiles and put the names of the second
958     into `tempfiles'. */
959 
960   for (i = 0; i < ntemps; i++)
961     {
962       char *newtemp = maketempname (++tempcount);
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