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