1 /* Process TeX index dribble output into an actual index. 2 $Id: texindex.c,v 1.4 2002/06/10 13:51:04 espie Exp $ 3 4 Copyright (C) 1987, 91, 92, 96, 97, 98, 99, 2000, 01, 02 5 Free Software Foundation, Inc. 6 7 This program is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 2, or (at your option) 10 any later version. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with this program; if not, write to the Free Software 19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307. */ 20 21 #include "system.h" 22 #include <getopt.h> 23 24 static char *program_name = "texindex"; 25 26 #if defined (emacs) 27 # include "../src/config.h" 28 /* Some s/os.h files redefine these. */ 29 # undef read 30 # undef close 31 # undef write 32 # undef open 33 #endif 34 35 #if !defined (HAVE_MEMSET) 36 #undef memset 37 #define memset(ptr, ignore, count) bzero (ptr, count) 38 #endif 39 40 char *mktemp (); 41 42 #if !defined (SEEK_SET) 43 # define SEEK_SET 0 44 # define SEEK_CUR 1 45 # define SEEK_END 2 46 #endif /* !SEEK_SET */ 47 48 /* When sorting in core, this structure describes one line 49 and the position and length of its first keyfield. */ 50 struct lineinfo 51 { 52 char *text; /* The actual text of the line. */ 53 union { 54 char *text; /* The start of the key (for textual comparison). */ 55 long number; /* The numeric value (for numeric comparison). */ 56 } key; 57 long keylen; /* Length of KEY field. */ 58 }; 59 60 /* This structure describes a field to use as a sort key. */ 61 struct keyfield 62 { 63 int startwords; /* Number of words to skip. */ 64 int startchars; /* Number of additional chars to skip. */ 65 int endwords; /* Number of words to ignore at end. */ 66 int endchars; /* Ditto for characters of last word. */ 67 char ignore_blanks; /* Non-zero means ignore spaces and tabs. */ 68 char fold_case; /* Non-zero means case doesn't matter. */ 69 char reverse; /* Non-zero means compare in reverse order. */ 70 char numeric; /* Non-zeros means field is ASCII numeric. */ 71 char positional; /* Sort according to file position. */ 72 char braced; /* Count balanced-braced groupings as fields. */ 73 }; 74 75 /* Vector of keyfields to use. */ 76 struct keyfield keyfields[3]; 77 78 /* Number of keyfields stored in that vector. */ 79 int num_keyfields = 3; 80 81 /* Vector of input file names, terminated with a null pointer. */ 82 char **infiles; 83 84 /* Vector of corresponding output file names, or NULL, meaning default it 85 (add an `s' to the end). */ 86 char **outfiles; 87 88 /* Length of `infiles'. */ 89 int num_infiles; 90 91 /* Pointer to the array of pointers to lines being sorted. */ 92 char **linearray; 93 94 /* The allocated length of `linearray'. */ 95 long nlines; 96 97 /* Directory to use for temporary files. On Unix, it ends with a slash. */ 98 char *tempdir; 99 100 /* Start of filename to use for temporary files. */ 101 char *tempbase; 102 103 /* Number of last temporary file. */ 104 int tempcount; 105 106 /* Number of last temporary file already deleted. 107 Temporary files are deleted by `flush_tempfiles' in order of creation. */ 108 int last_deleted_tempcount; 109 110 /* During in-core sort, this points to the base of the data block 111 which contains all the lines of data. */ 112 char *text_base; 113 114 /* Additional command switches .*/ 115 116 /* Nonzero means do not delete tempfiles -- for debugging. */ 117 int keep_tempfiles; 118 119 /* Forward declarations of functions in this file. */ 120 void decode_command (); 121 void sort_in_core (); 122 void sort_offline (); 123 char **parsefile (); 124 char *find_field (); 125 char *find_pos (); 126 long find_value (); 127 char *find_braced_pos (); 128 char *find_braced_end (); 129 void writelines (); 130 int compare_field (); 131 int compare_full (); 132 long readline (); 133 int merge_files (); 134 int merge_direct (); 135 void pfatal_with_name (); 136 void fatal (); 137 void error (); 138 void *xmalloc (), *xrealloc (); 139 char *concat (); 140 void flush_tempfiles (); 141 142 #define MAX_IN_CORE_SORT 500000 143 144 int 145 main (argc, argv) 146 int argc; 147 char **argv; 148 { 149 int i; 150 151 tempcount = 0; 152 last_deleted_tempcount = 0; 153 154 #ifdef HAVE_SETLOCALE 155 /* Set locale via LC_ALL. */ 156 setlocale (LC_ALL, ""); 157 #endif 158 159 /* Set the text message domain. */ 160 bindtextdomain (PACKAGE, LOCALEDIR); 161 textdomain (PACKAGE); 162 163 /* Describe the kind of sorting to do. */ 164 /* The first keyfield uses the first braced field and folds case. */ 165 keyfields[0].braced = 1; 166 keyfields[0].fold_case = 1; 167 keyfields[0].endwords = -1; 168 keyfields[0].endchars = -1; 169 170 /* The second keyfield uses the second braced field, numerically. */ 171 keyfields[1].braced = 1; 172 keyfields[1].numeric = 1; 173 keyfields[1].startwords = 1; 174 keyfields[1].endwords = -1; 175 keyfields[1].endchars = -1; 176 177 /* The third keyfield (which is ignored while discarding duplicates) 178 compares the whole line. */ 179 keyfields[2].endwords = -1; 180 keyfields[2].endchars = -1; 181 182 decode_command (argc, argv); 183 184 /* XXX mkstemp not appropriate, as we need to have somewhat predictable 185 * names. But race condition was fixed, see maketempname. 186 */ 187 tempbase = mktemp (concat ("txiXXXXXX", "", "")); 188 189 /* Process input files completely, one by one. */ 190 191 for (i = 0; i < num_infiles; i++) 192 { 193 int desc; 194 off_t ptr; 195 char *outfile; 196 struct stat instat; 197 198 desc = open (infiles[i], O_RDONLY, 0); 199 if (desc < 0) 200 pfatal_with_name (infiles[i]); 201 202 if (stat (infiles[i], &instat)) 203 pfatal_with_name (infiles[i]); 204 if (S_ISDIR (instat.st_mode)) 205 { 206 #ifdef EISDIR 207 errno = EISDIR; 208 #endif 209 pfatal_with_name (infiles[i]); 210 } 211 212 lseek (desc, (off_t) 0, SEEK_END); 213 ptr = (off_t) lseek (desc, (off_t) 0, SEEK_CUR); 214 215 close (desc); 216 217 outfile = outfiles[i]; 218 if (!outfile) 219 { 220 outfile = concat (infiles[i], "s", ""); 221 } 222 223 if (ptr < MAX_IN_CORE_SORT) 224 /* Sort a small amount of data. */ 225 sort_in_core (infiles[i], ptr, outfile); 226 else 227 sort_offline (infiles[i], ptr, outfile); 228 } 229 230 flush_tempfiles (tempcount); 231 xexit (0); 232 233 return 0; /* Avoid bogus warnings. */ 234 } 235 236 typedef struct 237 { 238 char *long_name; 239 char *short_name; 240 int *variable_ref; 241 int variable_value; 242 char *arg_name; 243 char *doc_string; 244 } TEXINDEX_OPTION; 245 246 TEXINDEX_OPTION texindex_options[] = { 247 { "--help", "-h", (int *)NULL, 0, (char *)NULL, 248 N_("display this help and exit") }, 249 { "--keep", "-k", &keep_tempfiles, 1, (char *)NULL, 250 N_("keep temporary files around after processing") }, 251 { "--no-keep", 0, &keep_tempfiles, 0, (char *)NULL, 252 N_("do not keep temporary files around after processing (default)") }, 253 { "--output", "-o", (int *)NULL, 0, "FILE", 254 N_("send output to FILE") }, 255 { "--version", (char *)NULL, (int *)NULL, 0, (char *)NULL, 256 N_("display version information and exit") }, 257 { (char *)NULL, (char *)NULL, (int *)NULL, 0, (char *)NULL } 258 }; 259 260 void 261 usage (result_value) 262 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 302 decode_command (argc, argv) 303 int argc; 304 char **argv; 305 { 306 int arg_index = 1; 307 char **ip; 308 char **op; 309 310 /* Store default values into parameter variables. */ 311 312 tempdir = getenv ("TMPDIR"); 313 if (tempdir == NULL) 314 tempdir = getenv ("TEMP"); 315 if (tempdir == NULL) 316 tempdir = getenv ("TMP"); 317 if (tempdir == NULL) 318 tempdir = DEFAULT_TMPDIR; 319 else 320 tempdir = concat (tempdir, "/", ""); 321 322 keep_tempfiles = 0; 323 324 /* Allocate ARGC input files, which must be enough. */ 325 326 infiles = (char **) xmalloc (argc * sizeof (char *)); 327 outfiles = (char **) xmalloc (argc * sizeof (char *)); 328 ip = infiles; 329 op = outfiles; 330 331 while (arg_index < argc) 332 { 333 char *arg = argv[arg_index++]; 334 335 if (*arg == '-') 336 { 337 if (strcmp (arg, "--version") == 0) 338 { 339 printf ("texindex (GNU %s) %s\n", PACKAGE, VERSION); 340 puts (""); 341 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ 342 There is NO warranty. You may redistribute this software\n\ 343 under the terms of the GNU General Public License.\n\ 344 For more information about these matters, see the files named COPYING.\n"), 345 "2002"); 346 xexit (0); 347 } 348 else if ((strcmp (arg, "--keep") == 0) || 349 (strcmp (arg, "-k") == 0)) 350 { 351 keep_tempfiles = 1; 352 } 353 else if ((strcmp (arg, "--help") == 0) || 354 (strcmp (arg, "-h") == 0)) 355 { 356 usage (0); 357 } 358 else if ((strcmp (arg, "--output") == 0) || 359 (strcmp (arg, "-o") == 0)) 360 { 361 if (argv[arg_index] != (char *)NULL) 362 { 363 arg_index++; 364 if (op > outfiles) 365 *(op - 1) = argv[arg_index]; 366 } 367 else 368 usage (1); 369 } 370 else 371 usage (1); 372 } 373 else 374 { 375 *ip++ = arg; 376 *op++ = (char *)NULL; 377 } 378 } 379 380 /* Record number of keyfields and terminate list of filenames. */ 381 num_infiles = ip - infiles; 382 *ip = (char *)NULL; 383 if (num_infiles == 0) 384 usage (1); 385 } 386 387 /* Return a name for a temporary file. */ 388 389 static char * 390 maketempname (count) 391 int count; 392 { 393 char tempsuffix[10]; 394 char *name; 395 int fd; 396 397 sprintf (tempsuffix, ".%d", count); 398 name = concat (tempdir, tempbase, tempsuffix); 399 400 fd = open (name, O_CREAT|O_EXCL|O_WRONLY, 0666); 401 if (fd == -1) 402 return NULL; 403 else 404 { 405 close(fd); 406 return name; 407 } 408 } 409 410 /* Delete all temporary files up to TO_COUNT. */ 411 412 void 413 flush_tempfiles (to_count) 414 int to_count; 415 { 416 if (keep_tempfiles) 417 return; 418 while (last_deleted_tempcount < to_count) 419 unlink (maketempname (++last_deleted_tempcount)); 420 } 421 422 423 /* Compare LINE1 and LINE2 according to the specified set of keyfields. */ 424 425 int 426 compare_full (line1, line2) 427 char **line1, **line2; 428 { 429 int i; 430 431 /* Compare using the first keyfield; 432 if that does not distinguish the lines, try the second keyfield; 433 and so on. */ 434 435 for (i = 0; i < num_keyfields; i++) 436 { 437 long length1, length2; 438 char *start1 = find_field (&keyfields[i], *line1, &length1); 439 char *start2 = find_field (&keyfields[i], *line2, &length2); 440 int tem = compare_field (&keyfields[i], start1, length1, *line1 - text_base, 441 start2, length2, *line2 - text_base); 442 if (tem) 443 { 444 if (keyfields[i].reverse) 445 return -tem; 446 return tem; 447 } 448 } 449 450 return 0; /* Lines match exactly. */ 451 } 452 453 /* Compare LINE1 and LINE2, described by structures 454 in which the first keyfield is identified in advance. 455 For positional sorting, assumes that the order of the lines in core 456 reflects their nominal order. */ 457 458 int 459 compare_prepared (line1, line2) 460 struct lineinfo *line1, *line2; 461 { 462 int i; 463 int tem; 464 char *text1, *text2; 465 466 /* Compare using the first keyfield, which has been found for us already. */ 467 if (keyfields->positional) 468 { 469 if (line1->text - text_base > line2->text - text_base) 470 tem = 1; 471 else 472 tem = -1; 473 } 474 else if (keyfields->numeric) 475 tem = line1->key.number - line2->key.number; 476 else 477 tem = compare_field (keyfields, line1->key.text, line1->keylen, 0, 478 line2->key.text, line2->keylen, 0); 479 if (tem) 480 { 481 if (keyfields->reverse) 482 return -tem; 483 return tem; 484 } 485 486 text1 = line1->text; 487 text2 = line2->text; 488 489 /* Compare using the second keyfield; 490 if that does not distinguish the lines, try the third keyfield; 491 and so on. */ 492 493 for (i = 1; i < num_keyfields; i++) 494 { 495 long length1, length2; 496 char *start1 = find_field (&keyfields[i], text1, &length1); 497 char *start2 = find_field (&keyfields[i], text2, &length2); 498 int tem = compare_field (&keyfields[i], start1, length1, text1 - text_base, 499 start2, length2, text2 - text_base); 500 if (tem) 501 { 502 if (keyfields[i].reverse) 503 return -tem; 504 return tem; 505 } 506 } 507 508 return 0; /* Lines match exactly. */ 509 } 510 511 /* Like compare_full but more general. 512 You can pass any strings, and you can say how many keyfields to use. 513 POS1 and POS2 should indicate the nominal positional ordering of 514 the two lines in the input. */ 515 516 int 517 compare_general (str1, str2, pos1, pos2, use_keyfields) 518 char *str1, *str2; 519 long pos1, pos2; 520 int use_keyfields; 521 { 522 int i; 523 524 /* Compare using the first keyfield; 525 if that does not distinguish the lines, try the second keyfield; 526 and so on. */ 527 528 for (i = 0; i < use_keyfields; i++) 529 { 530 long length1, length2; 531 char *start1 = find_field (&keyfields[i], str1, &length1); 532 char *start2 = find_field (&keyfields[i], str2, &length2); 533 int tem = compare_field (&keyfields[i], start1, length1, pos1, 534 start2, length2, pos2); 535 if (tem) 536 { 537 if (keyfields[i].reverse) 538 return -tem; 539 return tem; 540 } 541 } 542 543 return 0; /* Lines match exactly. */ 544 } 545 546 /* Find the start and length of a field in STR according to KEYFIELD. 547 A pointer to the starting character is returned, and the length 548 is stored into the int that LENGTHPTR points to. */ 549 550 char * 551 find_field (keyfield, str, lengthptr) 552 struct keyfield *keyfield; 553 char *str; 554 long *lengthptr; 555 { 556 char *start; 557 char *end; 558 char *(*fun) (); 559 560 if (keyfield->braced) 561 fun = find_braced_pos; 562 else 563 fun = find_pos; 564 565 start = (*fun) (str, keyfield->startwords, keyfield->startchars, 566 keyfield->ignore_blanks); 567 if (keyfield->endwords < 0) 568 { 569 if (keyfield->braced) 570 end = find_braced_end (start); 571 else 572 { 573 end = start; 574 while (*end && *end != '\n') 575 end++; 576 } 577 } 578 else 579 { 580 end = (*fun) (str, keyfield->endwords, keyfield->endchars, 0); 581 if (end - str < start - str) 582 end = start; 583 } 584 *lengthptr = end - start; 585 return start; 586 } 587 588 /* Return a pointer to a specified place within STR, 589 skipping (from the beginning) WORDS words and then CHARS chars. 590 If IGNORE_BLANKS is nonzero, we skip all blanks 591 after finding the specified word. */ 592 593 char * 594 find_pos (str, words, chars, ignore_blanks) 595 char *str; 596 int words, chars; 597 int ignore_blanks; 598 { 599 int i; 600 char *p = str; 601 602 for (i = 0; i < words; i++) 603 { 604 char c; 605 /* Find next bunch of nonblanks and skip them. */ 606 while ((c = *p) == ' ' || c == '\t') 607 p++; 608 while ((c = *p) && c != '\n' && !(c == ' ' || c == '\t')) 609 p++; 610 if (!*p || *p == '\n') 611 return p; 612 } 613 614 while (*p == ' ' || *p == '\t') 615 p++; 616 617 for (i = 0; i < chars; i++) 618 { 619 if (!*p || *p == '\n') 620 break; 621 p++; 622 } 623 return p; 624 } 625 626 /* Like find_pos but assumes that each field is surrounded by braces 627 and that braces within fields are balanced. */ 628 629 char * 630 find_braced_pos (str, words, chars, ignore_blanks) 631 char *str; 632 int words, chars; 633 int ignore_blanks; 634 { 635 int i; 636 int bracelevel; 637 char *p = str; 638 char c; 639 640 for (i = 0; i < words; i++) 641 { 642 bracelevel = 1; 643 while ((c = *p++) != '{' && c != '\n' && c) 644 /* Do nothing. */ ; 645 if (c != '{') 646 return p - 1; 647 while (bracelevel) 648 { 649 c = *p++; 650 if (c == '{') 651 bracelevel++; 652 if (c == '}') 653 bracelevel--; 654 if (c == 0 || c == '\n') 655 return p - 1; 656 } 657 } 658 659 while ((c = *p++) != '{' && c != '\n' && c) 660 /* Do nothing. */ ; 661 662 if (c != '{') 663 return p - 1; 664 665 if (ignore_blanks) 666 while ((c = *p) == ' ' || c == '\t') 667 p++; 668 669 for (i = 0; i < chars; i++) 670 { 671 if (!*p || *p == '\n') 672 break; 673 p++; 674 } 675 return p; 676 } 677 678 /* Find the end of the balanced-brace field which starts at STR. 679 The position returned is just before the closing brace. */ 680 681 char * 682 find_braced_end (str) 683 char *str; 684 { 685 int bracelevel; 686 char *p = str; 687 char c; 688 689 bracelevel = 1; 690 while (bracelevel) 691 { 692 c = *p++; 693 if (c == '{') 694 bracelevel++; 695 if (c == '}') 696 bracelevel--; 697 if (c == 0 || c == '\n') 698 return p - 1; 699 } 700 return p - 1; 701 } 702 703 long 704 find_value (start, length) 705 char *start; 706 long length; 707 { 708 while (length != 0L) 709 { 710 if (isdigit (*start)) 711 return atol (start); 712 length--; 713 start++; 714 } 715 return 0l; 716 } 717 718 /* Vector used to translate characters for comparison. 719 This is how we make all alphanumerics follow all else, 720 and ignore case in the first sorting. */ 721 int char_order[256]; 722 723 void 724 init_char_order () 725 { 726 int i; 727 for (i = 1; i < 256; i++) 728 char_order[i] = i; 729 730 for (i = '0'; i <= '9'; i++) 731 char_order[i] += 512; 732 733 for (i = 'a'; i <= 'z'; i++) 734 { 735 char_order[i] = 512 + i; 736 char_order[i + 'A' - 'a'] = 512 + i; 737 } 738 } 739 740 /* Compare two fields (each specified as a start pointer and a character count) 741 according to KEYFIELD. 742 The sign of the value reports the relation between the fields. */ 743 744 int 745 compare_field (keyfield, start1, length1, pos1, start2, length2, pos2) 746 struct keyfield *keyfield; 747 char *start1; 748 long length1; 749 long pos1; 750 char *start2; 751 long length2; 752 long pos2; 753 { 754 if (keyfields->positional) 755 { 756 if (pos1 > pos2) 757 return 1; 758 else 759 return -1; 760 } 761 if (keyfield->numeric) 762 { 763 long value = find_value (start1, length1) - find_value (start2, length2); 764 if (value > 0) 765 return 1; 766 if (value < 0) 767 return -1; 768 return 0; 769 } 770 else 771 { 772 char *p1 = start1; 773 char *p2 = start2; 774 char *e1 = start1 + length1; 775 char *e2 = start2 + length2; 776 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 (char_order[c1] != char_order[c2]) 791 return char_order[c1] - char_order[c2]; 792 if (!c1) 793 break; 794 } 795 796 /* Strings are equal except possibly for case. */ 797 p1 = start1; 798 p2 = start2; 799 while (1) 800 { 801 int c1, c2; 802 803 if (p1 == e1) 804 c1 = 0; 805 else 806 c1 = *p1++; 807 if (p2 == e2) 808 c2 = 0; 809 else 810 c2 = *p2++; 811 812 if (c1 != c2) 813 /* Reverse sign here so upper case comes out last. */ 814 return c2 - c1; 815 if (!c1) 816 break; 817 } 818 819 return 0; 820 } 821 } 822 823 /* A `struct linebuffer' is a structure which holds a line of text. 824 `readline' reads a line from a stream into a linebuffer 825 and works regardless of the length of the line. */ 826 827 struct linebuffer 828 { 829 long size; 830 char *buffer; 831 }; 832 833 /* Initialize LINEBUFFER for use. */ 834 835 void 836 initbuffer (linebuffer) 837 struct linebuffer *linebuffer; 838 { 839 linebuffer->size = 200; 840 linebuffer->buffer = (char *) xmalloc (200); 841 } 842 843 /* Read a line of text from STREAM into LINEBUFFER. 844 Return the length of the line. */ 845 846 long 847 readline (linebuffer, stream) 848 struct linebuffer *linebuffer; 849 FILE *stream; 850 { 851 char *buffer = linebuffer->buffer; 852 char *p = linebuffer->buffer; 853 char *end = p + linebuffer->size; 854 855 while (1) 856 { 857 int c = getc (stream); 858 if (p == end) 859 { 860 buffer = (char *) xrealloc (buffer, linebuffer->size *= 2); 861 p += buffer - linebuffer->buffer; 862 end += buffer - linebuffer->buffer; 863 linebuffer->buffer = buffer; 864 } 865 if (c < 0 || c == '\n') 866 { 867 *p = 0; 868 break; 869 } 870 *p++ = c; 871 } 872 873 return p - buffer; 874 } 875 876 /* Sort an input file too big to sort in core. */ 877 878 void 879 sort_offline (infile, nfiles, total, outfile) 880 char *infile; 881 int nfiles; 882 off_t total; 883 char *outfile; 884 { 885 /* More than enough. */ 886 int ntemps = 2 * (total + MAX_IN_CORE_SORT - 1) / MAX_IN_CORE_SORT; 887 char **tempfiles = (char **) xmalloc (ntemps * sizeof (char *)); 888 FILE *istream = fopen (infile, "r"); 889 int i; 890 struct linebuffer lb; 891 long linelength; 892 int failure = 0; 893 894 initbuffer (&lb); 895 896 /* Read in one line of input data. */ 897 898 linelength = readline (&lb, istream); 899 900 if (lb.buffer[0] != '\\' && lb.buffer[0] != '@') 901 { 902 error (_("%s: not a texinfo index file"), infile); 903 return; 904 } 905 906 /* Split up the input into `ntemps' temporary files, or maybe fewer, 907 and put the new files' names into `tempfiles' */ 908 909 for (i = 0; i < ntemps; i++) 910 { 911 char *outname = maketempname (++tempcount); 912 FILE *ostream; 913 long tempsize = 0; 914 915 if (!outname) 916 pfatal_with_name("temporary file"); 917 ostream = fopen (outname, "w"); 918 if (!outname || !ostream) 919 pfatal_with_name (outname); 920 tempfiles[i] = outname; 921 922 /* Copy lines into this temp file as long as it does not make file 923 "too big" or until there are no more lines. */ 924 925 while (tempsize + linelength + 1 <= MAX_IN_CORE_SORT) 926 { 927 tempsize += linelength + 1; 928 fputs (lb.buffer, ostream); 929 putc ('\n', ostream); 930 931 /* Read another line of input data. */ 932 933 linelength = readline (&lb, istream); 934 if (!linelength && feof (istream)) 935 break; 936 937 if (lb.buffer[0] != '\\' && lb.buffer[0] != '@') 938 { 939 error (_("%s: not a texinfo index file"), infile); 940 failure = 1; 941 goto fail; 942 } 943 } 944 fclose (ostream); 945 if (feof (istream)) 946 break; 947 } 948 949 free (lb.buffer); 950 951 fail: 952 /* Record number of temp files we actually needed. */ 953 954 ntemps = i; 955 956 /* Sort each tempfile into another tempfile. 957 Delete the first set of tempfiles and put the names of the second 958 into `tempfiles'. */ 959 960 for (i = 0; i < ntemps; i++) 961 { 962 char *newtemp = maketempname (++tempcount); 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