32 * Revision 5.18 1995/06/16 06:19:24 eggert 33 * Update FSF address. 34 * 35 * Revision 5.17 1995/06/01 16:23:43 eggert 36 * (main, preparejoin): Pass argument instead of using `join' static variable. 37 * (main): Add -kb. 38 * 39 * Revision 5.16 1994/03/17 14:05:48 eggert 40 * Move buffer-flushes out of critical sections, since they aren't critical. 41 * Use ORCSerror to clean up after a fatal error. Remove lint. 42 * Specify subprocess input via file descriptor, not file name. 43 * 44 * Revision 5.15 1993/11/09 17:40:15 eggert 45 * -V now prints version on stdout and exits. Don't print usage twice. 46 * 47 * Revision 5.14 1993/11/03 17:42:27 eggert 48 * Add -z. Generate a value for the Name keyword. 49 * Don't arbitrarily limit the number of joins. 50 * Improve quality of diagnostics. 51 * 52 * Revision 5.13 1992/07/28 16:12:44 eggert 53 * Add -V. Check that working and RCS files are distinct. 54 * 55 * Revision 5.12 1992/02/17 23:02:08 eggert 56 * Add -T. 57 * 58 * Revision 5.11 1992/01/24 18:44:19 eggert 59 * Add support for bad_creat0. lint -> RCS_lint 60 * 61 * Revision 5.10 1992/01/06 02:42:34 eggert 62 * Update usage string. 63 * 64 * Revision 5.9 1991/10/07 17:32:46 eggert 65 * -k affects just working file, not RCS file. 66 * 67 * Revision 5.8 1991/08/19 03:13:55 eggert 68 * Warn before removing somebody else's file. 69 * Add -M. Fix co -j bugs. Tune. 70 * 71 * Revision 5.7 1991/04/21 11:58:15 eggert 72 * Ensure that working file is newer than RCS file after co -[lu]. 73 * Add -x, RCSINIT, MS-DOS support. 74 * 75 * Revision 5.6 1990/12/04 05:18:38 eggert 76 * Don't checkaccesslist() unless necessary. 77 * Use -I for prompts and -q for diagnostics. 78 * 79 * Revision 5.5 1990/11/01 05:03:26 eggert 80 * Fix -j. Add -I. 81 * 82 * Revision 5.4 1990/10/04 06:30:11 eggert 83 * Accumulate exit status across files. 84 * 85 * Revision 5.3 1990/09/11 02:41:09 eggert 86 * co -kv yields a readonly working file. 87 * 88 * Revision 5.2 1990/09/04 08:02:13 eggert 89 * Standardize yes-or-no procedure. 90 * 91 * Revision 5.0 1990/08/22 08:10:02 eggert 92 * Permit multiple locks by same user. Add setuid support. 93 * Remove compile-time limits; use malloc instead. 94 * Permit dates past 1999/12/31. Switch to GMT. 95 * Make lock and temp files faster and safer. 96 * Ansify and Posixate. Add -k, -V. Remove snooping. Tune. 97 * 98 * Revision 4.7 89/05/01 15:11:41 narten 99 * changed copyright header to reflect current distribution rules 100 * 101 * Revision 4.6 88/08/09 19:12:15 eggert 102 * Fix "co -d" core dump; rawdate wasn't always initialized. 103 * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint 104 * 105 * Revision 4.5 87/12/18 11:35:40 narten 106 * lint cleanups (from Guy Harris) 107 * 108 * Revision 4.4 87/10/18 10:20:53 narten 109 * Updating version numbers changes relative to 1.1, are actually 110 * relative to 4.2 111 * 112 * Revision 1.3 87/09/24 13:58:30 narten 113 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 114 * warnings) 115 * 116 * Revision 1.2 87/03/27 14:21:38 jenkins 117 * Port to suns 118 * 119 * Revision 4.2 83/12/05 13:39:48 wft 120 * made rewriteflag external. 121 * 122 * Revision 4.1 83/05/10 16:52:55 wft 123 * Added option -u and -f. 124 * Added handling of default branch. 125 * Replaced getpwuid() with getcaller(). 126 * Removed calls to stat(); now done by pairfilenames(). 127 * Changed and renamed rmoldfile() to rmworkfile(). 128 * Replaced catchints() calls with restoreints(), unlink()--link() with rename(); 129 * 130 * Revision 3.7 83/02/15 15:27:07 wft 131 * Added call to fastcopy() to copy remainder of RCS file. 132 * 133 * Revision 3.6 83/01/15 14:37:50 wft 134 * Added ignoring of interrupts while RCS file is renamed; this avoids 135 * deletion of RCS files during the unlink/link window. 136 * 137 * Revision 3.5 82/12/08 21:40:11 wft 138 * changed processing of -d to use DATEFORM; removed actual from 139 * call to preparejoin; re-fixed printing of done at the end. 140 * 141 * Revision 3.4 82/12/04 18:40:00 wft 142 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. 143 * Fixed printing of "done". 144 * 145 * Revision 3.3 82/11/28 22:23:11 wft 146 * Replaced getlogin() with getpwuid(), flcose() with ffclose(), 147 * %02d with %.2d, mode generation for working file with WORKMODE. 148 * Fixed nil printing. Fixed -j combined with -l and -p, and exit 149 * for non-existing revisions in preparejoin(). 150 * 151 * Revision 3.2 82/10/18 20:47:21 wft 152 * Mode of working file is now maintained even for co -l, but write permission 153 * is removed. 154 * The working file inherits its mode from the RCS file, plus write permission 155 * for the owner. The write permission is not given if locking is strict and 156 * co does not lock. 157 * An existing working file without write permission is deleted automatically. 158 * Otherwise, co asks (empty answer: abort co). 159 * Call to getfullRCSname() added, check for write error added, call 160 * for getlogin() fixed. 161 * 162 * Revision 3.1 82/10/13 16:01:30 wft 163 * fixed type of variables receiving from getc() (char -> int). 164 * removed unused variables. 165 */ 166 167 168 169 170#include "rcsbase.h" 171 172static char *addjoin P((char*)); 173static char const *getancestor P((char const*,char const*)); 174static int buildjoin P((char const*)); 175static int preparejoin P((char*)); 176static int rmlock P((struct hshentry const*)); 177static int rmworkfile P((void)); 178static void cleanup P((void)); 179 180static char const quietarg[] = "-q"; 181
| 42 * Revision 5.18 1995/06/16 06:19:24 eggert 43 * Update FSF address. 44 * 45 * Revision 5.17 1995/06/01 16:23:43 eggert 46 * (main, preparejoin): Pass argument instead of using `join' static variable. 47 * (main): Add -kb. 48 * 49 * Revision 5.16 1994/03/17 14:05:48 eggert 50 * Move buffer-flushes out of critical sections, since they aren't critical. 51 * Use ORCSerror to clean up after a fatal error. Remove lint. 52 * Specify subprocess input via file descriptor, not file name. 53 * 54 * Revision 5.15 1993/11/09 17:40:15 eggert 55 * -V now prints version on stdout and exits. Don't print usage twice. 56 * 57 * Revision 5.14 1993/11/03 17:42:27 eggert 58 * Add -z. Generate a value for the Name keyword. 59 * Don't arbitrarily limit the number of joins. 60 * Improve quality of diagnostics. 61 * 62 * Revision 5.13 1992/07/28 16:12:44 eggert 63 * Add -V. Check that working and RCS files are distinct. 64 * 65 * Revision 5.12 1992/02/17 23:02:08 eggert 66 * Add -T. 67 * 68 * Revision 5.11 1992/01/24 18:44:19 eggert 69 * Add support for bad_creat0. lint -> RCS_lint 70 * 71 * Revision 5.10 1992/01/06 02:42:34 eggert 72 * Update usage string. 73 * 74 * Revision 5.9 1991/10/07 17:32:46 eggert 75 * -k affects just working file, not RCS file. 76 * 77 * Revision 5.8 1991/08/19 03:13:55 eggert 78 * Warn before removing somebody else's file. 79 * Add -M. Fix co -j bugs. Tune. 80 * 81 * Revision 5.7 1991/04/21 11:58:15 eggert 82 * Ensure that working file is newer than RCS file after co -[lu]. 83 * Add -x, RCSINIT, MS-DOS support. 84 * 85 * Revision 5.6 1990/12/04 05:18:38 eggert 86 * Don't checkaccesslist() unless necessary. 87 * Use -I for prompts and -q for diagnostics. 88 * 89 * Revision 5.5 1990/11/01 05:03:26 eggert 90 * Fix -j. Add -I. 91 * 92 * Revision 5.4 1990/10/04 06:30:11 eggert 93 * Accumulate exit status across files. 94 * 95 * Revision 5.3 1990/09/11 02:41:09 eggert 96 * co -kv yields a readonly working file. 97 * 98 * Revision 5.2 1990/09/04 08:02:13 eggert 99 * Standardize yes-or-no procedure. 100 * 101 * Revision 5.0 1990/08/22 08:10:02 eggert 102 * Permit multiple locks by same user. Add setuid support. 103 * Remove compile-time limits; use malloc instead. 104 * Permit dates past 1999/12/31. Switch to GMT. 105 * Make lock and temp files faster and safer. 106 * Ansify and Posixate. Add -k, -V. Remove snooping. Tune. 107 * 108 * Revision 4.7 89/05/01 15:11:41 narten 109 * changed copyright header to reflect current distribution rules 110 * 111 * Revision 4.6 88/08/09 19:12:15 eggert 112 * Fix "co -d" core dump; rawdate wasn't always initialized. 113 * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint 114 * 115 * Revision 4.5 87/12/18 11:35:40 narten 116 * lint cleanups (from Guy Harris) 117 * 118 * Revision 4.4 87/10/18 10:20:53 narten 119 * Updating version numbers changes relative to 1.1, are actually 120 * relative to 4.2 121 * 122 * Revision 1.3 87/09/24 13:58:30 narten 123 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 124 * warnings) 125 * 126 * Revision 1.2 87/03/27 14:21:38 jenkins 127 * Port to suns 128 * 129 * Revision 4.2 83/12/05 13:39:48 wft 130 * made rewriteflag external. 131 * 132 * Revision 4.1 83/05/10 16:52:55 wft 133 * Added option -u and -f. 134 * Added handling of default branch. 135 * Replaced getpwuid() with getcaller(). 136 * Removed calls to stat(); now done by pairfilenames(). 137 * Changed and renamed rmoldfile() to rmworkfile(). 138 * Replaced catchints() calls with restoreints(), unlink()--link() with rename(); 139 * 140 * Revision 3.7 83/02/15 15:27:07 wft 141 * Added call to fastcopy() to copy remainder of RCS file. 142 * 143 * Revision 3.6 83/01/15 14:37:50 wft 144 * Added ignoring of interrupts while RCS file is renamed; this avoids 145 * deletion of RCS files during the unlink/link window. 146 * 147 * Revision 3.5 82/12/08 21:40:11 wft 148 * changed processing of -d to use DATEFORM; removed actual from 149 * call to preparejoin; re-fixed printing of done at the end. 150 * 151 * Revision 3.4 82/12/04 18:40:00 wft 152 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. 153 * Fixed printing of "done". 154 * 155 * Revision 3.3 82/11/28 22:23:11 wft 156 * Replaced getlogin() with getpwuid(), flcose() with ffclose(), 157 * %02d with %.2d, mode generation for working file with WORKMODE. 158 * Fixed nil printing. Fixed -j combined with -l and -p, and exit 159 * for non-existing revisions in preparejoin(). 160 * 161 * Revision 3.2 82/10/18 20:47:21 wft 162 * Mode of working file is now maintained even for co -l, but write permission 163 * is removed. 164 * The working file inherits its mode from the RCS file, plus write permission 165 * for the owner. The write permission is not given if locking is strict and 166 * co does not lock. 167 * An existing working file without write permission is deleted automatically. 168 * Otherwise, co asks (empty answer: abort co). 169 * Call to getfullRCSname() added, check for write error added, call 170 * for getlogin() fixed. 171 * 172 * Revision 3.1 82/10/13 16:01:30 wft 173 * fixed type of variables receiving from getc() (char -> int). 174 * removed unused variables. 175 */ 176 177 178 179 180#include "rcsbase.h" 181 182static char *addjoin P((char*)); 183static char const *getancestor P((char const*,char const*)); 184static int buildjoin P((char const*)); 185static int preparejoin P((char*)); 186static int rmlock P((struct hshentry const*)); 187static int rmworkfile P((void)); 188static void cleanup P((void)); 189 190static char const quietarg[] = "-q"; 191
|
318 case 'k': /* set keyword expand mode */ 319 expandarg = *argv; 320 if (0 <= expmode) redefined('k'); 321 if (0 <= (expmode = str2expmode(a))) 322 break; 323 /* fall into */ 324 default: 325 unknown: 326 error("unknown option: %s%s", *argv, cmdusage); 327 328 }; 329 } /* end of option processing */ 330 331 /* Now handle all pathnames. */ 332 if (nerror) cleanup(); 333 else if (argc < 1) faterror("no input file%s", cmdusage); 334 else for (; 0 < argc; cleanup(), ++argv, --argc) { 335 ffree(); 336 337 if (pairnames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false) <= 0) 338 continue; 339 340 /* 341 * RCSname contains the name of the RCS file, and finptr 342 * points at it. workname contains the name of the working file. 343 * Also, RCSstat has been set. 344 */ 345 diagnose("%s --> %s\n", RCSname, tostdout?"standard output":workname); 346 347 workstatstat = -1; 348 if (tostdout) { 349# if OPEN_O_BINARY 350 int newmode = Expand==BINARY_EXPAND ? OPEN_O_BINARY : 0; 351 if (stdout_mode != newmode) { 352 stdout_mode = newmode; 353 oflush(); 354 VOID setmode(STDOUT_FILENO, newmode); 355 } 356# endif 357 neworkname = 0; 358 neworkptr = workstdout = stdout; 359 } else { 360 workstatstat = stat(workname, &workstat); 361 if (workstatstat == 0 && same_file(RCSstat, workstat, 0)) { 362 rcserror("RCS file is the same as working file %s.", 363 workname 364 ); 365 continue; 366 } 367 neworkname = makedirtemp(1); 368 if (!(neworkptr = fopenSafer(neworkname, FOPEN_W_WORK))) { 369 if (errno == EACCES) 370 workerror("permission denied on parent directory"); 371 else 372 eerror(neworkname); 373 continue; 374 } 375 } 376 377 gettree(); /* reads in the delta tree */ 378 379 if (!Head) { 380 /* no revisions; create empty file */ 381 diagnose("no revisions present; generating empty revision 0.0\n"); 382 if (lockflag) 383 warn( 384 "no revisions, so nothing can be %slocked", 385 lockflag < 0 ? "un" : "" 386 ); 387 Ozclose(&fcopy); 388 if (workstatstat == 0) 389 if (!rmworkfile()) continue; 390 changelock = 0; 391 newdate = 0; 392 } else { 393 int locks = lockflag ? findlock(false, &targetdelta) : 0; 394 if (rev) { 395 /* expand symbolic revision number */ 396 if (!expandsym(rev, &numericrev)) 397 continue; 398 } else { 399 switch (locks) { 400 default: 401 continue; 402 case 0: 403 bufscpy(&numericrev, Dbranch?Dbranch:""); 404 break; 405 case 1: 406 bufscpy(&numericrev, targetdelta->num); 407 break; 408 } 409 } 410 /* get numbers of deltas to be generated */ 411 if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas))) 412 continue; 413 /* check reservations */ 414 changelock = 415 lockflag < 0 ? 416 rmlock(targetdelta) 417 : lockflag == 0 ? 418 0 419 : 420 addlock(targetdelta, true); 421 422 if ( 423 changelock < 0 424 || (changelock && !checkaccesslist()) 425 || dorewrite(lockflag, changelock) != 0 426 ) 427 continue; 428 429 if (0 <= expmode) 430 Expand = expmode; 431 if (0 < lockflag && Expand == VAL_EXPAND) { 432 rcserror("cannot combine -kv and -l"); 433 continue; 434 } 435 436 if (joinflag && !preparejoin(joinflag)) 437 continue; 438 439 diagnose("revision %s%s\n",targetdelta->num, 440 0<lockflag ? " (locked)" : 441 lockflag<0 ? " (unlocked)" : ""); 442 443 /* Prepare to remove old working file if necessary. */ 444 if (workstatstat == 0) 445 if (!rmworkfile()) continue; 446 447 /* skip description */ 448 getdesc(false); /* don't echo*/ 449 450 locker_expansion = 0 < lockflag; 451 targetdelta->name = namedrev(rev, targetdelta); 452 joinname = buildrevision( 453 gendeltas, targetdelta, 454 joinflag&&tostdout ? (FILE*)0 : neworkptr, 455 Expand < MIN_UNEXPAND 456 ); 457# if !large_memory 458 if (fcopy == neworkptr) 459 fcopy = 0; /* Don't close it twice. */ 460# endif 461 if_advise_access(changelock && gendeltas->first!=targetdelta, 462 finptr, MADV_SEQUENTIAL 463 ); 464 465 if (donerewrite(changelock, 466 Ttimeflag ? RCSstat.st_mtime : (time_t)-1 467 ) != 0) 468 continue; 469 470 if (changelock) { 471 locks += lockflag; 472 if (1 < locks) 473 rcswarn("You now have %d locks.", locks); 474 } 475 476 newdate = targetdelta->date; 477 if (joinflag) { 478 newdate = 0; 479 if (!joinname) { 480 aflush(neworkptr); 481 joinname = neworkname; 482 } 483 if (Expand == BINARY_EXPAND) 484 workerror("merging binary files"); 485 if (!buildjoin(joinname)) 486 continue; 487 } 488 } 489 if (!tostdout) { 490 mode_t m = WORKMODE(RCSstat.st_mode, 491 ! (Expand==VAL_EXPAND || (lockflag<=0 && StrictLocks)) 492 ); 493 time_t t = mtimeflag&&newdate ? date2time(newdate) : (time_t)-1; 494 aflush(neworkptr); 495 ignoreints(); 496 r = chnamemod(&neworkptr, neworkname, workname, 1, m, t); 497 keepdirtemp(neworkname); 498 restoreints(); 499 if (r != 0) { 500 eerror(workname); 501 error("see %s", neworkname); 502 continue; 503 } 504 diagnose("done\n"); 505 } 506 } 507 508 tempunlink(); 509 Ofclose(workstdout); 510 exitmain(exitstatus); 511 512} /* end of main (co) */ 513 514 static void 515cleanup() 516{ 517 if (nerror) exitstatus = EXIT_FAILURE; 518 Izclose(&finptr); 519 ORCSclose(); 520# if !large_memory 521 if (fcopy!=workstdout) Ozclose(&fcopy); 522# endif 523 if (neworkptr!=workstdout) Ozclose(&neworkptr); 524 dirtempunlink(); 525} 526 527#if RCS_lint 528# define exiterr coExit 529#endif 530 void 531exiterr() 532{ 533 ORCSerror(); 534 dirtempunlink(); 535 tempunlink(); 536 _exit(EXIT_FAILURE); 537} 538 539 540/***************************************************************** 541 * The following routines are auxiliary routines 542 *****************************************************************/ 543 544 static int 545rmworkfile() 546/* 547 * Prepare to remove workname, if it exists, and if 548 * it is read-only. 549 * Otherwise (file writable): 550 * if !quietmode asks the user whether to really delete it (default: fail); 551 * otherwise failure. 552 * Returns true if permission is gotten. 553 */ 554{ 555 if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) { 556 /* File is writable */ 557 if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ", 558 workname, 559 myself(workstat.st_uid) ? "" : ", and you do not own it" 560 )) { 561 error(!quietflag && ttystdin() 562 ? "checkout aborted" 563 : "writable %s exists; checkout aborted", workname); 564 return false; 565 } 566 } 567 /* Actual unlink is done later by caller. */ 568 return true; 569} 570 571 572 static int 573rmlock(delta) 574 struct hshentry const *delta; 575/* Function: removes the lock held by caller on delta. 576 * Returns -1 if someone else holds the lock, 577 * 0 if there is no lock on delta, 578 * and 1 if a lock was found and removed. 579 */ 580{ register struct rcslock * next, * trail; 581 char const *num; 582 struct rcslock dummy; 583 int whomatch, nummatch; 584 585 num=delta->num; 586 dummy.nextlock=next=Locks; 587 trail = &dummy; 588 while (next) { 589 whomatch = strcmp(getcaller(), next->login); 590 nummatch=strcmp(num,next->delta->num); 591 if ((whomatch==0) && (nummatch==0)) break; 592 /*found a lock on delta by caller*/ 593 if ((whomatch!=0)&&(nummatch==0)) { 594 rcserror("revision %s locked by %s; use co -r or rcs -u", 595 num, next->login 596 ); 597 return -1; 598 } 599 trail=next; 600 next=next->nextlock; 601 } 602 if (next) { 603 /*found one; delete it */ 604 trail->nextlock=next->nextlock; 605 Locks=dummy.nextlock; 606 next->delta->lockedby = 0; 607 return 1; /*success*/ 608 } else return 0; /*no lock on delta*/ 609} 610 611 612 613 614/***************************************************************** 615 * The rest of the routines are for handling joins 616 *****************************************************************/ 617 618 619 static char * 620addjoin(joinrev) 621 char *joinrev; 622/* Add joinrev's number to joinlist, yielding address of char past joinrev, 623 * or 0 if no such revision exists. 624 */ 625{ 626 register char *j; 627 register struct hshentry *d; 628 char terminator; 629 struct buf numrev; 630 struct hshentries *joindeltas; 631 632 j = joinrev; 633 for (;;) { 634 switch (*j++) { 635 default: 636 continue; 637 case 0: 638 case ' ': case '\t': case '\n': 639 case ':': case ',': case ';': 640 break; 641 } 642 break; 643 } 644 terminator = *--j; 645 *j = 0; 646 bufautobegin(&numrev); 647 d = 0; 648 if (expandsym(joinrev, &numrev)) 649 d = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&joindeltas); 650 bufautoend(&numrev); 651 *j = terminator; 652 if (d) { 653 joinlist[++lastjoin] = d->num; 654 return j; 655 } 656 return 0; 657} 658 659 static int 660preparejoin(j) 661 register char *j; 662/* Parse join list J and place pointers to the 663 * revision numbers into joinlist. 664 */ 665{ 666 lastjoin= -1; 667 for (;;) { 668 while ((*j==' ')||(*j=='\t')||(*j==',')) j++; 669 if (*j=='\0') break; 670 if (lastjoin>=joinlength-2) { 671 joinlist = 672 (joinlength *= 2) == 0 673 ? tnalloc(char const *, joinlength = 16) 674 : trealloc(char const *, joinlist, joinlength); 675 } 676 if (!(j = addjoin(j))) return false; 677 while ((*j==' ') || (*j=='\t')) j++; 678 if (*j == ':') { 679 j++; 680 while((*j==' ') || (*j=='\t')) j++; 681 if (*j!='\0') { 682 if (!(j = addjoin(j))) return false; 683 } else { 684 rcsfaterror("join pair incomplete"); 685 } 686 } else { 687 if (lastjoin==0) { /* first pair */ 688 /* common ancestor missing */ 689 joinlist[1]=joinlist[0]; 690 lastjoin=1; 691 /*derive common ancestor*/ 692 if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1]))) 693 return false; 694 } else { 695 rcsfaterror("join pair incomplete"); 696 } 697 } 698 } 699 if (lastjoin < 1) 700 rcsfaterror("empty join"); 701 return true; 702} 703 704 705 706 static char const * 707getancestor(r1, r2) 708 char const *r1, *r2; 709/* Yield the common ancestor of r1 and r2 if successful, 0 otherwise. 710 * Work reliably only if r1 and r2 are not branch numbers. 711 */ 712{ 713 static struct buf t1, t2; 714 715 int l1, l2, l3; 716 char const *r; 717 718 l1 = countnumflds(r1); 719 l2 = countnumflds(r2); 720 if ((2<l1 || 2<l2) && cmpnum(r1,r2)!=0) { 721 /* not on main trunk or identical */ 722 l3 = 0; 723 while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0) 724 l3 += 2; 725 /* This will terminate since r1 and r2 are not the same; see above. */ 726 if (l3==0) { 727 /* no common prefix; common ancestor on main trunk */ 728 VOID partialno(&t1, r1, l1>2 ? 2 : l1); 729 VOID partialno(&t2, r2, l2>2 ? 2 : l2); 730 r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string; 731 if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0) 732 return r; 733 } else if (cmpnumfld(r1, r2, l3+1)!=0) 734 return partialno(&t1,r1,l3); 735 } 736 rcserror("common ancestor of %s and %s undefined", r1, r2); 737 return 0; 738} 739 740 741 742 static int 743buildjoin(initialfile) 744 char const *initialfile; 745/* Function: merge pairs of elements in joinlist into initialfile 746 * If workstdout is set, copy result to stdout. 747 * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink(). 748 */ 749{ 750 struct buf commarg; 751 struct buf subs; 752 char const *rev2, *rev3; 753 int i; 754 char const *cov[10], *mergev[11]; 755 char const **p; 756 757 bufautobegin(&commarg); 758 bufautobegin(&subs); 759 rev2 = maketemp(0); 760 rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */ 761 762 cov[1] = CO; 763 /* cov[2] setup below */ 764 p = &cov[3]; 765 if (expandarg) *p++ = expandarg; 766 if (suffixarg) *p++ = suffixarg; 767 if (versionarg) *p++ = versionarg; 768 if (zonearg) *p++ = zonearg; 769 *p++ = quietarg; 770 *p++ = RCSname; 771 *p = 0; 772 773 mergev[1] = MERGE; 774 mergev[2] = mergev[4] = "-L"; 775 /* rest of mergev setup below */ 776 777 i=0; 778 while (i<lastjoin) { 779 /*prepare marker for merge*/ 780 if (i==0) 781 bufscpy(&subs, targetdelta->num); 782 else { 783 bufscat(&subs, ","); 784 bufscat(&subs, joinlist[i-2]); 785 bufscat(&subs, ":"); 786 bufscat(&subs, joinlist[i-1]); 787 } 788 diagnose("revision %s\n",joinlist[i]); 789 bufscpy(&commarg, "-p"); 790 bufscat(&commarg, joinlist[i]); 791 cov[2] = commarg.string; 792 if (runv(-1, rev2, cov)) 793 goto badmerge; 794 diagnose("revision %s\n",joinlist[i+1]); 795 bufscpy(&commarg, "-p"); 796 bufscat(&commarg, joinlist[i+1]); 797 cov[2] = commarg.string; 798 if (runv(-1, rev3, cov)) 799 goto badmerge; 800 diagnose("merging...\n"); 801 mergev[3] = subs.string; 802 mergev[5] = joinlist[i+1]; 803 p = &mergev[6]; 804 if (quietflag) *p++ = quietarg; 805 if (lastjoin<=i+2 && workstdout) *p++ = "-p"; 806 *p++ = initialfile; 807 *p++ = rev2; 808 *p++ = rev3; 809 *p = 0; 810 switch (runv(-1, (char*)0, mergev)) { 811 case DIFF_FAILURE: case DIFF_SUCCESS: 812 break; 813 default: 814 goto badmerge; 815 } 816 i=i+2; 817 } 818 bufautoend(&commarg); 819 bufautoend(&subs); 820 return true; 821 822 badmerge: 823 nerror++; 824 bufautoend(&commarg); 825 bufautoend(&subs); 826 return false; 827}
| 333 case 'k': /* set keyword expand mode */ 334 expandarg = *argv; 335 if (0 <= expmode) redefined('k'); 336 if (0 <= (expmode = str2expmode(a))) 337 break; 338 /* fall into */ 339 default: 340 unknown: 341 error("unknown option: %s%s", *argv, cmdusage); 342 343 }; 344 } /* end of option processing */ 345 346 /* Now handle all pathnames. */ 347 if (nerror) cleanup(); 348 else if (argc < 1) faterror("no input file%s", cmdusage); 349 else for (; 0 < argc; cleanup(), ++argv, --argc) { 350 ffree(); 351 352 if (pairnames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false) <= 0) 353 continue; 354 355 /* 356 * RCSname contains the name of the RCS file, and finptr 357 * points at it. workname contains the name of the working file. 358 * Also, RCSstat has been set. 359 */ 360 diagnose("%s --> %s\n", RCSname, tostdout?"standard output":workname); 361 362 workstatstat = -1; 363 if (tostdout) { 364# if OPEN_O_BINARY 365 int newmode = Expand==BINARY_EXPAND ? OPEN_O_BINARY : 0; 366 if (stdout_mode != newmode) { 367 stdout_mode = newmode; 368 oflush(); 369 VOID setmode(STDOUT_FILENO, newmode); 370 } 371# endif 372 neworkname = 0; 373 neworkptr = workstdout = stdout; 374 } else { 375 workstatstat = stat(workname, &workstat); 376 if (workstatstat == 0 && same_file(RCSstat, workstat, 0)) { 377 rcserror("RCS file is the same as working file %s.", 378 workname 379 ); 380 continue; 381 } 382 neworkname = makedirtemp(1); 383 if (!(neworkptr = fopenSafer(neworkname, FOPEN_W_WORK))) { 384 if (errno == EACCES) 385 workerror("permission denied on parent directory"); 386 else 387 eerror(neworkname); 388 continue; 389 } 390 } 391 392 gettree(); /* reads in the delta tree */ 393 394 if (!Head) { 395 /* no revisions; create empty file */ 396 diagnose("no revisions present; generating empty revision 0.0\n"); 397 if (lockflag) 398 warn( 399 "no revisions, so nothing can be %slocked", 400 lockflag < 0 ? "un" : "" 401 ); 402 Ozclose(&fcopy); 403 if (workstatstat == 0) 404 if (!rmworkfile()) continue; 405 changelock = 0; 406 newdate = 0; 407 } else { 408 int locks = lockflag ? findlock(false, &targetdelta) : 0; 409 if (rev) { 410 /* expand symbolic revision number */ 411 if (!expandsym(rev, &numericrev)) 412 continue; 413 } else { 414 switch (locks) { 415 default: 416 continue; 417 case 0: 418 bufscpy(&numericrev, Dbranch?Dbranch:""); 419 break; 420 case 1: 421 bufscpy(&numericrev, targetdelta->num); 422 break; 423 } 424 } 425 /* get numbers of deltas to be generated */ 426 if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas))) 427 continue; 428 /* check reservations */ 429 changelock = 430 lockflag < 0 ? 431 rmlock(targetdelta) 432 : lockflag == 0 ? 433 0 434 : 435 addlock(targetdelta, true); 436 437 if ( 438 changelock < 0 439 || (changelock && !checkaccesslist()) 440 || dorewrite(lockflag, changelock) != 0 441 ) 442 continue; 443 444 if (0 <= expmode) 445 Expand = expmode; 446 if (0 < lockflag && Expand == VAL_EXPAND) { 447 rcserror("cannot combine -kv and -l"); 448 continue; 449 } 450 451 if (joinflag && !preparejoin(joinflag)) 452 continue; 453 454 diagnose("revision %s%s\n",targetdelta->num, 455 0<lockflag ? " (locked)" : 456 lockflag<0 ? " (unlocked)" : ""); 457 458 /* Prepare to remove old working file if necessary. */ 459 if (workstatstat == 0) 460 if (!rmworkfile()) continue; 461 462 /* skip description */ 463 getdesc(false); /* don't echo*/ 464 465 locker_expansion = 0 < lockflag; 466 targetdelta->name = namedrev(rev, targetdelta); 467 joinname = buildrevision( 468 gendeltas, targetdelta, 469 joinflag&&tostdout ? (FILE*)0 : neworkptr, 470 Expand < MIN_UNEXPAND 471 ); 472# if !large_memory 473 if (fcopy == neworkptr) 474 fcopy = 0; /* Don't close it twice. */ 475# endif 476 if_advise_access(changelock && gendeltas->first!=targetdelta, 477 finptr, MADV_SEQUENTIAL 478 ); 479 480 if (donerewrite(changelock, 481 Ttimeflag ? RCSstat.st_mtime : (time_t)-1 482 ) != 0) 483 continue; 484 485 if (changelock) { 486 locks += lockflag; 487 if (1 < locks) 488 rcswarn("You now have %d locks.", locks); 489 } 490 491 newdate = targetdelta->date; 492 if (joinflag) { 493 newdate = 0; 494 if (!joinname) { 495 aflush(neworkptr); 496 joinname = neworkname; 497 } 498 if (Expand == BINARY_EXPAND) 499 workerror("merging binary files"); 500 if (!buildjoin(joinname)) 501 continue; 502 } 503 } 504 if (!tostdout) { 505 mode_t m = WORKMODE(RCSstat.st_mode, 506 ! (Expand==VAL_EXPAND || (lockflag<=0 && StrictLocks)) 507 ); 508 time_t t = mtimeflag&&newdate ? date2time(newdate) : (time_t)-1; 509 aflush(neworkptr); 510 ignoreints(); 511 r = chnamemod(&neworkptr, neworkname, workname, 1, m, t); 512 keepdirtemp(neworkname); 513 restoreints(); 514 if (r != 0) { 515 eerror(workname); 516 error("see %s", neworkname); 517 continue; 518 } 519 diagnose("done\n"); 520 } 521 } 522 523 tempunlink(); 524 Ofclose(workstdout); 525 exitmain(exitstatus); 526 527} /* end of main (co) */ 528 529 static void 530cleanup() 531{ 532 if (nerror) exitstatus = EXIT_FAILURE; 533 Izclose(&finptr); 534 ORCSclose(); 535# if !large_memory 536 if (fcopy!=workstdout) Ozclose(&fcopy); 537# endif 538 if (neworkptr!=workstdout) Ozclose(&neworkptr); 539 dirtempunlink(); 540} 541 542#if RCS_lint 543# define exiterr coExit 544#endif 545 void 546exiterr() 547{ 548 ORCSerror(); 549 dirtempunlink(); 550 tempunlink(); 551 _exit(EXIT_FAILURE); 552} 553 554 555/***************************************************************** 556 * The following routines are auxiliary routines 557 *****************************************************************/ 558 559 static int 560rmworkfile() 561/* 562 * Prepare to remove workname, if it exists, and if 563 * it is read-only. 564 * Otherwise (file writable): 565 * if !quietmode asks the user whether to really delete it (default: fail); 566 * otherwise failure. 567 * Returns true if permission is gotten. 568 */ 569{ 570 if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) { 571 /* File is writable */ 572 if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ", 573 workname, 574 myself(workstat.st_uid) ? "" : ", and you do not own it" 575 )) { 576 error(!quietflag && ttystdin() 577 ? "checkout aborted" 578 : "writable %s exists; checkout aborted", workname); 579 return false; 580 } 581 } 582 /* Actual unlink is done later by caller. */ 583 return true; 584} 585 586 587 static int 588rmlock(delta) 589 struct hshentry const *delta; 590/* Function: removes the lock held by caller on delta. 591 * Returns -1 if someone else holds the lock, 592 * 0 if there is no lock on delta, 593 * and 1 if a lock was found and removed. 594 */ 595{ register struct rcslock * next, * trail; 596 char const *num; 597 struct rcslock dummy; 598 int whomatch, nummatch; 599 600 num=delta->num; 601 dummy.nextlock=next=Locks; 602 trail = &dummy; 603 while (next) { 604 whomatch = strcmp(getcaller(), next->login); 605 nummatch=strcmp(num,next->delta->num); 606 if ((whomatch==0) && (nummatch==0)) break; 607 /*found a lock on delta by caller*/ 608 if ((whomatch!=0)&&(nummatch==0)) { 609 rcserror("revision %s locked by %s; use co -r or rcs -u", 610 num, next->login 611 ); 612 return -1; 613 } 614 trail=next; 615 next=next->nextlock; 616 } 617 if (next) { 618 /*found one; delete it */ 619 trail->nextlock=next->nextlock; 620 Locks=dummy.nextlock; 621 next->delta->lockedby = 0; 622 return 1; /*success*/ 623 } else return 0; /*no lock on delta*/ 624} 625 626 627 628 629/***************************************************************** 630 * The rest of the routines are for handling joins 631 *****************************************************************/ 632 633 634 static char * 635addjoin(joinrev) 636 char *joinrev; 637/* Add joinrev's number to joinlist, yielding address of char past joinrev, 638 * or 0 if no such revision exists. 639 */ 640{ 641 register char *j; 642 register struct hshentry *d; 643 char terminator; 644 struct buf numrev; 645 struct hshentries *joindeltas; 646 647 j = joinrev; 648 for (;;) { 649 switch (*j++) { 650 default: 651 continue; 652 case 0: 653 case ' ': case '\t': case '\n': 654 case ':': case ',': case ';': 655 break; 656 } 657 break; 658 } 659 terminator = *--j; 660 *j = 0; 661 bufautobegin(&numrev); 662 d = 0; 663 if (expandsym(joinrev, &numrev)) 664 d = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&joindeltas); 665 bufautoend(&numrev); 666 *j = terminator; 667 if (d) { 668 joinlist[++lastjoin] = d->num; 669 return j; 670 } 671 return 0; 672} 673 674 static int 675preparejoin(j) 676 register char *j; 677/* Parse join list J and place pointers to the 678 * revision numbers into joinlist. 679 */ 680{ 681 lastjoin= -1; 682 for (;;) { 683 while ((*j==' ')||(*j=='\t')||(*j==',')) j++; 684 if (*j=='\0') break; 685 if (lastjoin>=joinlength-2) { 686 joinlist = 687 (joinlength *= 2) == 0 688 ? tnalloc(char const *, joinlength = 16) 689 : trealloc(char const *, joinlist, joinlength); 690 } 691 if (!(j = addjoin(j))) return false; 692 while ((*j==' ') || (*j=='\t')) j++; 693 if (*j == ':') { 694 j++; 695 while((*j==' ') || (*j=='\t')) j++; 696 if (*j!='\0') { 697 if (!(j = addjoin(j))) return false; 698 } else { 699 rcsfaterror("join pair incomplete"); 700 } 701 } else { 702 if (lastjoin==0) { /* first pair */ 703 /* common ancestor missing */ 704 joinlist[1]=joinlist[0]; 705 lastjoin=1; 706 /*derive common ancestor*/ 707 if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1]))) 708 return false; 709 } else { 710 rcsfaterror("join pair incomplete"); 711 } 712 } 713 } 714 if (lastjoin < 1) 715 rcsfaterror("empty join"); 716 return true; 717} 718 719 720 721 static char const * 722getancestor(r1, r2) 723 char const *r1, *r2; 724/* Yield the common ancestor of r1 and r2 if successful, 0 otherwise. 725 * Work reliably only if r1 and r2 are not branch numbers. 726 */ 727{ 728 static struct buf t1, t2; 729 730 int l1, l2, l3; 731 char const *r; 732 733 l1 = countnumflds(r1); 734 l2 = countnumflds(r2); 735 if ((2<l1 || 2<l2) && cmpnum(r1,r2)!=0) { 736 /* not on main trunk or identical */ 737 l3 = 0; 738 while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0) 739 l3 += 2; 740 /* This will terminate since r1 and r2 are not the same; see above. */ 741 if (l3==0) { 742 /* no common prefix; common ancestor on main trunk */ 743 VOID partialno(&t1, r1, l1>2 ? 2 : l1); 744 VOID partialno(&t2, r2, l2>2 ? 2 : l2); 745 r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string; 746 if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0) 747 return r; 748 } else if (cmpnumfld(r1, r2, l3+1)!=0) 749 return partialno(&t1,r1,l3); 750 } 751 rcserror("common ancestor of %s and %s undefined", r1, r2); 752 return 0; 753} 754 755 756 757 static int 758buildjoin(initialfile) 759 char const *initialfile; 760/* Function: merge pairs of elements in joinlist into initialfile 761 * If workstdout is set, copy result to stdout. 762 * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink(). 763 */ 764{ 765 struct buf commarg; 766 struct buf subs; 767 char const *rev2, *rev3; 768 int i; 769 char const *cov[10], *mergev[11]; 770 char const **p; 771 772 bufautobegin(&commarg); 773 bufautobegin(&subs); 774 rev2 = maketemp(0); 775 rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */ 776 777 cov[1] = CO; 778 /* cov[2] setup below */ 779 p = &cov[3]; 780 if (expandarg) *p++ = expandarg; 781 if (suffixarg) *p++ = suffixarg; 782 if (versionarg) *p++ = versionarg; 783 if (zonearg) *p++ = zonearg; 784 *p++ = quietarg; 785 *p++ = RCSname; 786 *p = 0; 787 788 mergev[1] = MERGE; 789 mergev[2] = mergev[4] = "-L"; 790 /* rest of mergev setup below */ 791 792 i=0; 793 while (i<lastjoin) { 794 /*prepare marker for merge*/ 795 if (i==0) 796 bufscpy(&subs, targetdelta->num); 797 else { 798 bufscat(&subs, ","); 799 bufscat(&subs, joinlist[i-2]); 800 bufscat(&subs, ":"); 801 bufscat(&subs, joinlist[i-1]); 802 } 803 diagnose("revision %s\n",joinlist[i]); 804 bufscpy(&commarg, "-p"); 805 bufscat(&commarg, joinlist[i]); 806 cov[2] = commarg.string; 807 if (runv(-1, rev2, cov)) 808 goto badmerge; 809 diagnose("revision %s\n",joinlist[i+1]); 810 bufscpy(&commarg, "-p"); 811 bufscat(&commarg, joinlist[i+1]); 812 cov[2] = commarg.string; 813 if (runv(-1, rev3, cov)) 814 goto badmerge; 815 diagnose("merging...\n"); 816 mergev[3] = subs.string; 817 mergev[5] = joinlist[i+1]; 818 p = &mergev[6]; 819 if (quietflag) *p++ = quietarg; 820 if (lastjoin<=i+2 && workstdout) *p++ = "-p"; 821 *p++ = initialfile; 822 *p++ = rev2; 823 *p++ = rev3; 824 *p = 0; 825 switch (runv(-1, (char*)0, mergev)) { 826 case DIFF_FAILURE: case DIFF_SUCCESS: 827 break; 828 default: 829 goto badmerge; 830 } 831 i=i+2; 832 } 833 bufautoend(&commarg); 834 bufautoend(&subs); 835 return true; 836 837 badmerge: 838 nerror++; 839 bufautoend(&commarg); 840 bufautoend(&subs); 841 return false; 842}
|