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