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