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