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