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