1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright (c) 1995 Sun Microsystems, Inc. All Rights Reserved 24 * 25 * module: 26 * recon.c 27 * 28 * purpose: 29 * process the reconciliation list, figure out exactly what the 30 * changes were, and what we should do about them. 31 * 32 * contents: 33 * reconcile ... (top level) process the reconciliation list 34 * samedata .... (static) do two files have the same contents 35 * samestuff ... (static) do two files have the same ownership/protection 36 * samecompare . (static) actually read and compare the contents 37 * samelink .... (static) do two symlinks have the same contents 38 * truncated ... (static) was one of the two copies truncted 39 * older ....... (static) which copy is older 40 * newer ....... (static) which copy is newer 41 * full_name ... generate a full path name for a file 42 * 43 * notes: 44 * If you only study one routine in this whole program, reconcile 45 * is that routine. Everything else is just book keeping. 46 * 47 * things were put onto the reconciliation list because analyze 48 * thought that they might have changed ... but up until now 49 * nobody has figured out what the changes really were, or even 50 * if there really were any changes. 51 * 52 * queue_file has ordered the reconciliation list with directory 53 * creations first (depth ordered) and deletions last (inversely 54 * depth ordered). all other changes have been ordered by mod time. 55 */ 56 #ident "%W% %E% SMI" 57 58 #include <stdio.h> 59 #include <unistd.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <fcntl.h> 63 64 #include "filesync.h" 65 #include "database.h" 66 #include "messages.h" 67 #include "debug.h" 68 69 /* 70 * local routines to figure out how the files really differ 71 */ 72 static bool_t samedata(struct file *); 73 static bool_t samestuff(struct file *); 74 static bool_t samecompare(struct file *); 75 static bool_t truncated(struct file *); 76 static bool_t samelink(); 77 static side_t newer(struct file *); 78 static side_t older(struct file *); 79 80 /* 81 * globals 82 */ 83 char *srcname; /* file we are emulating */ 84 char *dstname; /* file we are updating */ 85 86 /* 87 * routine: 88 * reconcile 89 * 90 * purpose: 91 * to perform the reconciliation action associated with a file 92 * 93 * parameters: 94 * file pointer 95 * 96 * returns: 97 * built up error mask 98 * updated statistics 99 * 100 * notes: 101 * The switch statement handles the obvious stuff. 102 * The TRUE side of the samedata test handles minor differences. 103 * The interesting stuff is in the FALSE side of the samedata test. 104 * 105 * The desparation heuristics (in the diffmask&CONTENTS test) are 106 * not rigorously correct ... but they always try do the right thing 107 * with data, and only lose mode/ownership changes in relatively 108 * pathological cases. But I claim that the benefits outweigh the 109 * risks, and most users will be pleased with the resulting decisions. 110 * 111 * Another trick is in the deletion cases of the switch. We 112 * normally won't allow an unlink that conflicts with data 113 * changes. If there are multiple links to the file, however, 114 * we can make the changes and do the deletion. 115 * 116 * The action routines do_{remove,rename,like,copy} handle all 117 * of their own statistics and status updating. This routine 118 * only has to handle its own reconciliation failures (when we 119 * can't decide what to do). 120 */ 121 errmask_t 122 reconcile(struct file *fp) 123 { errmask_t errs = 0; 124 diffmask_t diffmask; 125 126 if (opt_debug & DBG_RECON) 127 fprintf(stderr, "RECO: %s flgs=%s, mtime=%08lx.%08lx\n", 128 fp->f_fullname, 129 showflags(fileflags, fp->f_flags), 130 fp->f_modtime, fp->f_modns); 131 132 /* 133 * form the fully qualified names for both files 134 */ 135 srcname = full_name(fp, OPT_SRC, OPT_SRC); 136 dstname = full_name(fp, OPT_DST, OPT_DST); 137 138 /* 139 * because they are so expensive to read and so troublesome 140 * to set, we try to put off reading ACLs as long as possible. 141 * If we haven't read them yet, we must read them now (so that 142 * samestuff can compare them). 143 */ 144 if (opt_acls == 0 && fp->f_info[ OPT_BASE ].f_numacls == 0) { 145 if (get_acls(srcname, &fp->f_info[ OPT_SRC ])) 146 fp->f_srcdiffs |= D_FACLS; 147 if (get_acls(dstname, &fp->f_info[ OPT_DST ])) 148 fp->f_dstdiffs |= D_FACLS; 149 } 150 151 /* 152 * If a rename has been detected, we don't have to figure 153 * it out, since both the rename-to and rename-from files 154 * have already been designated. When we encounter a rename-to 155 * we should carry it out. When we encounter a rename-from 156 * we can ignore it, since it should be dealt with as a side 157 * effect of processing the rename-to. 158 */ 159 if ((fp->f_srcdiffs|fp->f_dstdiffs) & D_RENAME_FROM) 160 return (0); 161 162 if ((fp->f_srcdiffs|fp->f_dstdiffs) & D_RENAME_TO) { 163 164 if (opt_verbose) 165 fprintf(stdout, gettext(V_renamed), 166 fp->f_previous->f_fullname, fp->f_name); 167 168 if (fp->f_srcdiffs & D_RENAME_TO) { 169 errs = do_rename(fp, OPT_DST); 170 fp->f_srcdiffs &= D_MTIME | D_SIZE; 171 } else if (fp->f_dstdiffs & D_RENAME_TO) { 172 errs = do_rename(fp, OPT_SRC); 173 fp->f_dstdiffs &= D_MTIME | D_SIZE; 174 } 175 176 if (errs != ERR_RESOLVABLE) 177 goto done; 178 179 /* 180 * if any differences remain, then we may be dealing 181 * with contents changes in addition to a rename 182 */ 183 if ((fp->f_srcdiffs | fp->f_dstdiffs) == 0) 184 goto done; 185 186 /* 187 * fall through to reconcile the data changes 188 */ 189 } 190 191 /* 192 * pull of the easy cases (non-conflict creations & deletions) 193 */ 194 switch (fp->f_flags & (F_WHEREFOUND)) { 195 case F_IN_BASELINE: /* only exists in baseline */ 196 case 0: /* only exists in rules */ 197 if (opt_verbose) 198 fprintf(stdout, gettext(V_nomore), 199 fp->f_fullname); 200 fp->f_flags |= F_REMOVE; /* fix baseline */ 201 return (0); 202 203 case F_IN_BASELINE|F_IN_SOURCE: /* deleted from dest */ 204 /* 205 * the basic principle here is that we are willing 206 * to do the deletion if: 207 * no changes were made on the other side 208 * OR 209 * we have been told to force in this direction 210 * 211 * we do, however, make an exception for files that 212 * will still have other links. In this case, the 213 * (changed) data will still be accessable through 214 * another link and so we are willing to do the unlink 215 * inspite of conflicting changes (which may well 216 * have been introduced through another link. 217 * 218 * The jury is still out on this one 219 */ 220 if (((fp->f_srcdiffs&D_IMPORTANT) == 0) || 221 (opt_force == OPT_DST) || 222 has_other_links(fp, OPT_SRC)) { 223 if (opt_verbose) 224 fprintf(stdout, gettext(V_deleted), 225 fp->f_fullname, "dst"); 226 errs = do_remove(fp, OPT_SRC); 227 goto done; 228 } 229 230 /* a deletion combined with changes */ 231 if (opt_verbose) 232 fprintf(stdout, gettext(V_delconf), 233 fp->f_fullname); 234 235 /* if we are to resolve in favor of source */ 236 if (opt_force == OPT_SRC) { 237 errs = do_copy(fp, OPT_DST); 238 goto done; 239 } 240 241 fp->f_problem = gettext(PROB_del_change); 242 goto cant; 243 244 case F_IN_BASELINE|F_IN_DEST: /* deleted from src */ 245 /* just like previous case, w/sides reversed */ 246 if (((fp->f_dstdiffs&D_IMPORTANT) == 0) || 247 (opt_force == OPT_SRC) || 248 has_other_links(fp, OPT_DST)) { 249 if (opt_verbose) 250 fprintf(stdout, gettext(V_deleted), 251 fp->f_fullname, "src"); 252 errs = do_remove(fp, OPT_DST); 253 goto done; 254 } 255 256 /* a deletion combined with changes */ 257 if (opt_verbose) 258 fprintf(stdout, gettext(V_delconf), 259 fp->f_fullname); 260 261 /* if we are to resolve in favor of destination */ 262 if (opt_force == OPT_DST) { 263 errs = do_copy(fp, OPT_SRC); 264 goto done; 265 } 266 267 fp->f_problem = gettext(PROB_del_change); 268 goto cant; 269 270 /* 271 * if something new shows up, and for some reason we cannot 272 * propagate it to the other side, we should suppress the 273 * file from the baseline, so it will show up as a new 274 * creation next time too. 275 */ 276 case F_IN_SOURCE: /* created in src */ 277 if (opt_verbose) 278 fprintf(stdout, gettext(V_created), 279 fp->f_fullname, "src"); 280 errs = do_copy(fp, OPT_DST); 281 goto done; 282 283 case F_IN_DEST: /* created in dest */ 284 if (opt_verbose) 285 fprintf(stdout, gettext(V_created), 286 fp->f_fullname, "dst"); 287 errs = do_copy(fp, OPT_SRC); 288 goto done; 289 290 case F_IN_SOURCE|F_IN_DEST: /* not in baseline */ 291 /* 292 * since we don't have a baseline, we cannot 293 * know which of the two copies should prevail 294 */ 295 break; 296 297 case F_IN_BASELINE|F_IN_SOURCE|F_IN_DEST: 298 /* 299 * we have a baseline where the two copies agreed, 300 * so maybe we can determine that only one of the 301 * two copies have changed ... but before we decide 302 * who should be the winner we should determine 303 * that the two copies are actually different. 304 */ 305 break; 306 } 307 308 /* 309 * if we have fallen out of the case statement, it is because 310 * we have discovered a non-obvious situation where potentially 311 * changed versions of the file exist on both sides. 312 * 313 * if the two copies turn out to be identical, this is simple 314 */ 315 if (samedata(fp)) { 316 if (samestuff(fp)) { 317 /* files are identical, just update baseline */ 318 if (opt_verbose) 319 fprintf(stdout, gettext(V_unchanged), 320 fp->f_fullname); 321 update_info(fp, OPT_SRC); 322 goto done; 323 } else { 324 /* 325 * contents agree but ownership/protection does 326 * not agree, so we have to bring these into 327 * agreement. We can pick a winner if one 328 * side hasn't changed, or if the user has 329 * specified a force flag. 330 */ 331 if (opt_verbose) 332 fprintf(stdout, gettext(V_modes), 333 fp->f_fullname); 334 335 if (((fp->f_srcdiffs & D_ADMIN) == 0) || 336 (opt_force == OPT_DST)) { 337 errs = do_like(fp, OPT_SRC, TRUE); 338 goto done; 339 } 340 341 if (((fp->f_dstdiffs & D_ADMIN) == 0) || 342 (opt_force == OPT_SRC)) { 343 errs = do_like(fp, OPT_DST, TRUE); 344 goto done; 345 } 346 } 347 /* falls down to cant */ 348 } else { 349 /* 350 * The two files have different contents, so we have 351 * a potential conflict here. If we know that only one 352 * side has changed, we go with that side. 353 */ 354 if (fp->f_dstdiffs == 0 || fp->f_srcdiffs == 0) { 355 if (opt_verbose) 356 fprintf(stdout, gettext(V_changed), 357 fp->f_fullname); 358 errs = do_copy(fp, fp->f_srcdiffs ? OPT_DST : OPT_SRC); 359 goto done; 360 } 361 362 /* 363 * Both sides have changed, so we have a real conflict. 364 */ 365 if (opt_verbose) 366 fprintf(stdout, 367 gettext(truncated(fp) ? 368 V_trunconf : V_different), 369 fp->f_fullname); 370 371 /* 372 * See if the user has given us explicit instructions 373 * on how to resolve conflicts. We may have been told 374 * to favor the older, the newer, the source, or the 375 * destination ... but the default is to leave the 376 * conflict unresolved. 377 */ 378 if (opt_force == OPT_OLD) { 379 errs = do_copy(fp, newer(fp)); 380 goto done; 381 } 382 383 if (opt_force == OPT_NEW) { 384 errs = do_copy(fp, older(fp)); 385 goto done; 386 } 387 388 if (opt_force != 0) { 389 errs = do_copy(fp, (opt_force == OPT_SRC) ? 390 OPT_DST : OPT_SRC); 391 goto done; 392 } 393 394 395 /* 396 * This is our last chance before giving up. 397 * 398 * We know that the files have different contents and 399 * that there were changes on both sides. The only way 400 * we can safely handle this is if there were pure contents 401 * changes on one side and pure ownership changes on the 402 * other side. In this case we can propagate the ownership 403 * one way and the contents the other way. 404 * 405 * We decide whether or not this is possible by ANDing 406 * together the changes on the two sides, and seeing 407 * if the changes were all orthogonal (none of the same 408 * things changed on both sides). 409 */ 410 diffmask = fp->f_srcdiffs & fp->f_dstdiffs; 411 if ((diffmask & D_CONTENTS) == 0) { 412 /* 413 * if ownership changes were only made on one side 414 * (presumably the side that didn't have data changes) 415 * we can handle them separately. In this case, 416 * ownership changes must be fixed first, because 417 * the subsequent do_copy will overwrite them. 418 */ 419 if ((diffmask & D_ADMIN) == 0) 420 errs |= do_like(fp, (fp->f_srcdiffs&D_ADMIN) ? 421 OPT_DST : OPT_SRC, 422 TRUE); 423 424 /* 425 * Now we can deal with the propagation of the data 426 * changes. Note that any ownership/protection 427 * changes (from the other side) that have not been 428 * propagated yet are about to be lost. The cases 429 * in which this might happen are all pathological 430 * and the consequences of losing the protection 431 * changes are (IMHO) minor when compared to the 432 * obviously correct data propagation. 433 */ 434 errs |= do_copy(fp, (fp->f_srcdiffs&D_CONTENTS) ? 435 OPT_DST : OPT_SRC); 436 goto done; 437 } 438 439 /* 440 * there are conflicting changes, nobody has told us how to 441 * resolve conflicts, and we cannot figure out how to merge 442 * the differences. 443 */ 444 fp->f_problem = gettext(PROB_different); 445 } 446 447 cant: 448 /* 449 * I'm not smart enough to resolve this conflict automatically, 450 * so I have no choice but to bounce it back to the user. 451 */ 452 fp->f_flags |= F_CONFLICT; 453 fp->f_base->b_unresolved++; 454 errs |= ERR_UNRESOLVED; 455 456 done: 457 /* 458 * if we have a conflict and the file is not in the baseline, 459 * then there was never any point at which the two copies were 460 * in agreement, and we want to preserve the conflict for future 461 * resolution. 462 */ 463 if ((errs&ERR_UNRESOLVED) && (fp->f_flags & F_IN_BASELINE) == 0) 464 if (fp->f_files == 0) 465 /* 466 * in most cases, this is most easily done by just 467 * excluding the file in question from the baseline 468 */ 469 fp->f_flags |= F_REMOVE; 470 else 471 /* 472 * but ... if the file in question is a directory 473 * with children, excluding it from the baseline 474 * would keep all of its children (even those with 475 * no conflicts) out of the baseline as well. In 476 * This case, it is better to tell a lie and to 477 * manufacture a point of imaginary agreement 478 * in the baseline ... but one that is absurd enough 479 * that we will still see conflicts each time we run. 480 * 481 * recording a type of directory, and everything 482 * else as zero should be absurd enough. 483 */ 484 fp->f_info[ OPT_BASE ].f_type = S_IFDIR; 485 486 if (opt_debug & DBG_MISC) 487 fprintf(stderr, "MISC: %s ERRS=%s\n", fp->f_fullname, 488 showflags(errmap, errs)); 489 490 return (errs); 491 } 492 493 /* 494 * routine: 495 * newer 496 * 497 * purpose: 498 * determine which of two files is newer 499 * 500 * parameters: 501 * struct file 502 * 503 * returns: 504 * side_t (src/dest) 505 */ 506 static side_t 507 newer(struct file *fp) 508 { 509 struct fileinfo *sp, *dp; 510 511 sp = &fp->f_info[OPT_SRC]; 512 dp = &fp->f_info[OPT_DST]; 513 514 if (sp->f_modtime > dp->f_modtime) 515 return (OPT_SRC); 516 517 if (sp->f_modtime < dp->f_modtime) 518 return (OPT_DST); 519 520 if (sp->f_modns >= dp->f_modns) 521 return (OPT_SRC); 522 523 return (OPT_DST); 524 } 525 526 /* 527 * routine: 528 * older 529 * 530 * purpose: 531 * determine which of two files is older 532 * 533 * parameters: 534 * struct file 535 * 536 * returns: 537 * side_t (src/dest) 538 */ 539 static side_t 540 older(struct file *fp) 541 { 542 struct fileinfo *sp, *dp; 543 544 sp = &fp->f_info[OPT_SRC]; 545 dp = &fp->f_info[OPT_DST]; 546 547 if (sp->f_modtime < dp->f_modtime) 548 return (OPT_SRC); 549 550 if (sp->f_modtime > dp->f_modtime) 551 return (OPT_DST); 552 553 if (sp->f_modns <= dp->f_modns) 554 return (OPT_SRC); 555 556 return (OPT_DST); 557 } 558 559 /* 560 * routine: 561 * samedata 562 * 563 * purpose: 564 * determine whether or not two files contain the same data 565 * 566 * parameters: 567 * struct file 568 * 569 * returns: 570 * bool_t (true/false) 571 */ 572 static bool_t 573 samedata(struct file *fp) 574 { 575 struct fileinfo *sp, *dp; 576 577 sp = &fp->f_info[OPT_SRC]; 578 dp = &fp->f_info[OPT_DST]; 579 580 /* cheap test: types are different */ 581 if (sp->f_type != dp->f_type) 582 return (FALSE); 583 584 /* cheap test: directories have same contents */ 585 if (sp->f_type == S_IFDIR) 586 return (TRUE); 587 588 /* special files are compared via their maj/min */ 589 if ((sp->f_type == S_IFBLK) || (sp->f_type == S_IFCHR)) { 590 if (sp->f_rd_maj != dp->f_rd_maj) 591 return (FALSE); 592 if (sp->f_rd_min != dp->f_rd_min) 593 return (FALSE); 594 return (TRUE); 595 } 596 597 /* symlinks are the same if their contents are the same */ 598 if (sp->f_type == S_IFLNK) 599 return (samelink()); 600 601 /* cheap test: sizes are different */ 602 if (fp->f_info[OPT_SRC].f_size != fp->f_info[OPT_DST].f_size) 603 return (FALSE); 604 605 /* expensive test: byte for byte comparison */ 606 if (samecompare(fp) == 0) 607 return (FALSE); 608 609 return (TRUE); 610 } 611 612 /* 613 * routine: 614 * samestuff 615 * 616 * purpose: 617 * determine whether or not two files have same owner/protection 618 * 619 * parameters: 620 * struct file 621 * 622 * returns: 623 * bool_t (true/false) 624 */ 625 static bool_t 626 samestuff(struct file *fp) 627 { int same_mode, same_uid, same_gid, same_acl; 628 struct fileinfo *sp, *dp; 629 630 sp = &fp->f_info[OPT_SRC]; 631 dp = &fp->f_info[OPT_DST]; 632 633 same_mode = (sp->f_mode == dp->f_mode); 634 same_uid = (sp->f_uid == dp->f_uid); 635 same_gid = (sp->f_gid == dp->f_gid); 636 same_acl = cmp_acls(sp, dp); 637 638 /* if the are all the same, it is easy to tell the truth */ 639 if (same_uid && same_gid && same_mode && same_acl) 640 return (TRUE); 641 642 /* note the nature of the conflict */ 643 if (!same_uid || !same_gid || !same_acl) 644 fp->f_problem = gettext(PROB_ownership); 645 else 646 fp->f_problem = gettext(PROB_protection); 647 648 return (FALSE); 649 } 650 651 /* 652 * routine: 653 * samecompare 654 * 655 * purpose: 656 * do a byte-for-byte comparison of two files 657 * 658 * parameters: 659 * struct file 660 * 661 * returns: 662 * bool_t (true/false) 663 */ 664 static bool_t 665 samecompare(struct file *fp) 666 { int sfd, dfd; 667 int i, count; 668 char srcbuf[ COPY_BSIZE ], dstbuf[ COPY_BSIZE ]; 669 bool_t same = TRUE; 670 671 672 sfd = open(srcname, 0); 673 if (sfd < 0) 674 return (FALSE); 675 676 dfd = open(dstname, 0); 677 if (dfd < 0) { 678 close(sfd); 679 return (FALSE); 680 } 681 682 for ( 683 count = read(sfd, srcbuf, COPY_BSIZE); 684 count > 0; 685 count = read(sfd, srcbuf, COPY_BSIZE)) { 686 687 /* do a matching read */ 688 if (read(dfd, dstbuf, COPY_BSIZE) != count) { 689 same = FALSE; 690 goto done; 691 } 692 693 /* do the comparison for this block */ 694 for (i = 0; i < count; i++) { 695 if (srcbuf[i] != dstbuf[i]) { 696 same = FALSE; 697 goto done; 698 } 699 } 700 } 701 702 done: 703 if (opt_debug & DBG_ANAL) 704 fprintf(stderr, "ANAL: SAME=%d %s\n", same, fp->f_fullname); 705 706 close(sfd); 707 close(dfd); 708 return (same); 709 } 710 711 /* 712 * routine: 713 * truncated 714 * 715 * purpose: 716 * to determine whether or not a file has been truncated 717 * 718 * parameters: 719 * pointer to file structure 720 * 721 * returns: 722 * true/false 723 */ 724 static bool_t 725 truncated(struct file *fp) 726 { 727 /* either source or destination must now be zero length */ 728 if (fp->f_info[OPT_SRC].f_size && fp->f_info[OPT_DST].f_size) 729 return (FALSE); 730 731 /* file must have originally had a non-zero length */ 732 if (fp->f_info[OPT_BASE].f_size == 0) 733 return (FALSE); 734 735 /* file type must "normal" all around */ 736 if (fp->f_info[OPT_BASE].f_type != S_IFREG) 737 return (FALSE); 738 if (fp->f_info[OPT_SRC].f_type != S_IFREG) 739 return (FALSE); 740 if (fp->f_info[OPT_DST].f_type != S_IFREG) 741 return (FALSE); 742 743 744 return (TRUE); 745 } 746 747 /* 748 * routine: 749 * samelink 750 * 751 * purpose: 752 * to determine whether or not two symbolic links agree 753 * 754 * parameters: 755 * pointer to file structure 756 * 757 * returns: 758 * true/false 759 */ 760 static bool_t 761 samelink() 762 { int i, srclen, dstlen; 763 char srcbuf[ MAX_PATH ], dstbuf[ MAX_PATH ]; 764 765 766 /* read both copies of the link */ 767 srclen = readlink(srcname, srcbuf, sizeof (srcbuf)); 768 dstlen = readlink(dstname, dstbuf, sizeof (dstbuf)); 769 770 /* if they aren't the same length, they disagree */ 771 if (srclen < 0 || dstlen < 0 || srclen != dstlen) 772 return (FALSE); 773 774 /* look for differences in contents */ 775 for (i = 0; i < srclen; i++) 776 if (srcbuf[i] != dstbuf[i]) 777 return (FALSE); 778 779 return (TRUE); 780 } 781 782 /* 783 * routine: 784 * full_name 785 * 786 * purpose: 787 * to figure out the fully qualified path name to a file on the 788 * reconciliation list. 789 * 790 * parameters: 791 * pointer to the file structure 792 * side indication for which base to use 793 * side indication for which buffer to use 794 * 795 * returns: 796 * pointer to a clobberable buffer 797 * 798 * notes: 799 * the zero'th buffer is used for renames and links, where 800 * we need the name of another file on the same side. 801 */ 802 char * 803 full_name(struct file *fp, side_t srcdst, side_t whichbuf) 804 { static char *buffers[3]; 805 static int buflen = 0; 806 char *p, *b; 807 int l; 808 809 /* see if the existing buffer is long enough */ 810 b = (srcdst == OPT_SRC) ? fp->f_base->b_src_name 811 : fp->f_base->b_dst_name; 812 813 /* see if the allocated buffer is long enough */ 814 l = strlen(b) + strlen(fp->f_fullname) + 2; 815 if (l > buflen) { 816 /* figure out the next "nice" size to use */ 817 for (buflen = MAX_PATH; buflen < l; buflen += MAX_NAME); 818 819 /* reallocate all buffers to this size */ 820 for (l = 0; l < 3; l++) { 821 buffers[l] = (char *) realloc(buffers[l], buflen); 822 if (buffers[l] == 0) 823 nomem("full name"); 824 } 825 } 826 827 /* assemble the name in the buffer and reurn it */ 828 p = buffers[whichbuf]; 829 strcpy(p, b); 830 strcat(p, "/"); 831 strcat(p, fp->f_fullname); 832 return (p); 833 }