co.c revision 10
1/* Copyright (C) 1982, 1988, 1989 Walter Tichy 2 Copyright 1990, 1991 by Paul Eggert 3 Distributed under license by the Free Software Foundation, Inc. 4 5This file is part of RCS. 6 7RCS is free software; you can redistribute it and/or modify 8it under the terms of the GNU General Public License as published by 9the Free Software Foundation; either version 2, or (at your option) 10any later version. 11 12RCS is distributed in the hope that it will be useful, 13but WITHOUT ANY WARRANTY; without even the implied warranty of 14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15GNU General Public License for more details. 16 17You should have received a copy of the GNU General Public License 18along with RCS; see the file COPYING. If not, write to 19the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 20 21Report problems and direct all questions to: 22 23 rcs-bugs@cs.purdue.edu 24 25*/ 26 27/* 28 * RCS checkout operation 29 */ 30/***************************************************************************** 31 * check out revisions from RCS files 32 ***************************************************************************** 33 */ 34 35 36/* $Log: co.c,v $ 37 * Revision 5.9 1991/10/07 17:32:46 eggert 38 * ci -u src/RCS/co.c,v src/co.c <<\. 39 * -k affects just working file, not RCS file. 40 * 41 * Revision 5.8 1991/08/19 03:13:55 eggert 42 * Warn before removing somebody else's file. 43 * Add -M. Fix co -j bugs. Tune. 44 * 45 * Revision 5.7 1991/04/21 11:58:15 eggert 46 * Ensure that working file is newer than RCS file after co -[lu]. 47 * Add -x, RCSINIT, MS-DOS support. 48 * 49 * Revision 5.6 1990/12/04 05:18:38 eggert 50 * Don't checkaccesslist() unless necessary. 51 * Use -I for prompts and -q for diagnostics. 52 * 53 * Revision 5.5 1990/11/01 05:03:26 eggert 54 * Fix -j. Add -I. 55 * 56 * Revision 5.4 1990/10/04 06:30:11 eggert 57 * Accumulate exit status across files. 58 * 59 * Revision 5.3 1990/09/11 02:41:09 eggert 60 * co -kv yields a readonly working file. 61 * 62 * Revision 5.2 1990/09/04 08:02:13 eggert 63 * Standardize yes-or-no procedure. 64 * 65 * Revision 5.0 1990/08/22 08:10:02 eggert 66 * Permit multiple locks by same user. Add setuid support. 67 * Remove compile-time limits; use malloc instead. 68 * Permit dates past 1999/12/31. Switch to GMT. 69 * Make lock and temp files faster and safer. 70 * Ansify and Posixate. Add -k, -V. Remove snooping. Tune. 71 * 72 * Revision 4.7 89/05/01 15:11:41 narten 73 * changed copyright header to reflect current distribution rules 74 * 75 * Revision 4.6 88/08/09 19:12:15 eggert 76 * Fix "co -d" core dump; rawdate wasn't always initialized. 77 * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint 78 * 79 * Revision 4.5 87/12/18 11:35:40 narten 80 * lint cleanups (from Guy Harris) 81 * 82 * Revision 4.4 87/10/18 10:20:53 narten 83 * Updating version numbers changes relative to 1.1, are actually 84 * relative to 4.2 85 * 86 * Revision 1.3 87/09/24 13:58:30 narten 87 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 88 * warnings) 89 * 90 * Revision 1.2 87/03/27 14:21:38 jenkins 91 * Port to suns 92 * 93 * Revision 4.2 83/12/05 13:39:48 wft 94 * made rewriteflag external. 95 * 96 * Revision 4.1 83/05/10 16:52:55 wft 97 * Added option -u and -f. 98 * Added handling of default branch. 99 * Replaced getpwuid() with getcaller(). 100 * Removed calls to stat(); now done by pairfilenames(). 101 * Changed and renamed rmoldfile() to rmworkfile(). 102 * Replaced catchints() calls with restoreints(), unlink()--link() with rename(); 103 * 104 * Revision 3.7 83/02/15 15:27:07 wft 105 * Added call to fastcopy() to copy remainder of RCS file. 106 * 107 * Revision 3.6 83/01/15 14:37:50 wft 108 * Added ignoring of interrupts while RCS file is renamed; this avoids 109 * deletion of RCS files during the unlink/link window. 110 * 111 * Revision 3.5 82/12/08 21:40:11 wft 112 * changed processing of -d to use DATEFORM; removed actual from 113 * call to preparejoin; re-fixed printing of done at the end. 114 * 115 * Revision 3.4 82/12/04 18:40:00 wft 116 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. 117 * Fixed printing of "done". 118 * 119 * Revision 3.3 82/11/28 22:23:11 wft 120 * Replaced getlogin() with getpwuid(), flcose() with ffclose(), 121 * %02d with %.2d, mode generation for working file with WORKMODE. 122 * Fixed nil printing. Fixed -j combined with -l and -p, and exit 123 * for non-existing revisions in preparejoin(). 124 * 125 * Revision 3.2 82/10/18 20:47:21 wft 126 * Mode of working file is now maintained even for co -l, but write permission 127 * is removed. 128 * The working file inherits its mode from the RCS file, plus write permission 129 * for the owner. The write permission is not given if locking is strict and 130 * co does not lock. 131 * An existing working file without write permission is deleted automatically. 132 * Otherwise, co asks (empty answer: abort co). 133 * Call to getfullRCSname() added, check for write error added, call 134 * for getlogin() fixed. 135 * 136 * Revision 3.1 82/10/13 16:01:30 wft 137 * fixed type of variables receiving from getc() (char -> int). 138 * removed unused variables. 139 */ 140 141 142 143 144#include "rcsbase.h" 145 146static char const *getancestor P((char const*,char const*)); 147static int buildjoin P((char const*)); 148static int preparejoin P((void)); 149static int rmlock P((struct hshentry const*)); 150static int rmworkfile P((void)); 151static void cleanup P((void)); 152 153static char const quietarg[] = "-q"; 154 155static char const *expandarg, *join, *suffixarg, *versionarg; 156static char const *joinlist[joinlength]; /* revisions to be joined */ 157static FILE *neworkptr; 158static int exitstatus; 159static int forceflag; 160static int lastjoin; /* index of last element in joinlist */ 161static int lockflag; /* -1 -> unlock, 0 -> do nothing, 1 -> lock */ 162static int mtimeflag; 163static struct hshentries *gendeltas; /* deltas to be generated */ 164static struct hshentry *targetdelta; /* final delta to be generated */ 165static struct stat workstat; 166 167mainProg(coId, "co", "$Id: co.c,v 5.9 1991/10/07 17:32:46 eggert Exp $") 168{ 169 static char const cmdusage[] = 170 "\nco usage: co -{flpqru}[rev] -ddate -jjoinlist -sstate -w[login] -Vn file ..."; 171 172 char *a, **newargv; 173 char const *author, *date, *rev, *state; 174 char const *joinfilename, *newdate, *neworkfilename; 175 int changelock; /* 1 if a lock has been changed, -1 if error */ 176 int expmode, r, tostdout, workstatstat; 177 struct buf numericrev; /* expanded revision number */ 178 char finaldate[datesize]; 179 180 setrid(); 181 author = date = rev = state = nil; 182 bufautobegin(&numericrev); 183 expmode = -1; 184 suffixes = X_DEFAULT; 185 tostdout = false; 186 187 argc = getRCSINIT(argc, argv, &newargv); 188 argv = newargv; 189 while (a = *++argv, 0<--argc && *a++=='-') { 190 switch (*a++) { 191 192 case 'r': 193 revno: 194 if (*a) { 195 if (rev) warn("redefinition of revision number"); 196 rev = a; 197 } 198 break; 199 200 case 'f': 201 forceflag=true; 202 goto revno; 203 204 case 'l': 205 if (lockflag < 0) { 206 warn("-l overrides -u."); 207 } 208 lockflag = 1; 209 goto revno; 210 211 case 'u': 212 if (0 < lockflag) { 213 warn("-l overrides -u."); 214 } 215 lockflag = -1; 216 goto revno; 217 218 case 'p': 219 tostdout = true; 220 goto revno; 221 222 case 'I': 223 interactiveflag = true; 224 goto revno; 225 226 case 'q': 227 quietflag=true; 228 goto revno; 229 230 case 'd': 231 if (date) 232 redefined('d'); 233 str2date(a, finaldate); 234 date=finaldate; 235 break; 236 237 case 'j': 238 if (*a) { 239 if (join) redefined('j'); 240 join = a; 241 } 242 break; 243 244 case 'M': 245 mtimeflag = true; 246 goto revno; 247 248 case 's': 249 if (*a) { 250 if (state) redefined('s'); 251 state = a; 252 } 253 break; 254 255 case 'w': 256 if (author) redefined('w'); 257 if (*a) 258 author = a; 259 else 260 author = getcaller(); 261 break; 262 263 case 'x': 264 suffixarg = *argv; 265 suffixes = a; 266 break; 267 268 case 'V': 269 versionarg = *argv; 270 setRCSversion(versionarg); 271 break; 272 273 case 'k': /* set keyword expand mode */ 274 expandarg = *argv; 275 if (0 <= expmode) redefined('k'); 276 if (0 <= (expmode = str2expmode(a))) 277 break; 278 /* fall into */ 279 default: 280 faterror("unknown option: %s%s", *argv, cmdusage); 281 282 }; 283 } /* end of option processing */ 284 285 if (argc<1) faterror("no input file%s", cmdusage); 286 if (tostdout) 287# if text_equals_binary_stdio || text_work_stdio 288 workstdout = stdout; 289# else 290 if (!(workstdout = fdopen(STDOUT_FILENO, FOPEN_W_WORK))) 291 efaterror("stdout"); 292# endif 293 294 /* now handle all filenames */ 295 do { 296 ffree(); 297 298 if (pairfilenames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false) <= 0) 299 continue; 300 301 /* now RCSfilename contains the name of the RCS file, and finptr 302 * points at it. workfilename contains the name of the working file. 303 * Also, RCSstat has been set. 304 */ 305 diagnose("%s --> %s\n", RCSfilename,tostdout?"stdout":workfilename); 306 307 workstatstat = -1; 308 if (tostdout) { 309 neworkfilename = 0; 310 neworkptr = workstdout; 311 } else { 312 workstatstat = stat(workfilename, &workstat); 313 neworkfilename = makedirtemp(workfilename, 1); 314 if (!(neworkptr = fopen(neworkfilename, FOPEN_W_WORK))) { 315 if (errno == EACCES) 316 error("%s: parent directory isn't writable", 317 workfilename 318 ); 319 else 320 eerror(neworkfilename); 321 continue; 322 } 323 } 324 325 gettree(); /* reads in the delta tree */ 326 327 if (Head==nil) { 328 /* no revisions; create empty file */ 329 diagnose("no revisions present; generating empty revision 0.0\n"); 330 Ozclose(&fcopy); 331 if (workstatstat == 0) 332 if (!rmworkfile()) continue; 333 changelock = 0; 334 newdate = 0; 335 /* Can't reserve a delta, so don't call addlock */ 336 } else { 337 if (rev!=nil) { 338 /* expand symbolic revision number */ 339 if (!expandsym(rev, &numericrev)) 340 continue; 341 } else 342 switch (lockflag<0 ? findlock(false,&targetdelta) : 0) { 343 default: 344 continue; 345 case 0: 346 bufscpy(&numericrev, Dbranch?Dbranch:""); 347 break; 348 case 1: 349 bufscpy(&numericrev, targetdelta->num); 350 break; 351 } 352 /* get numbers of deltas to be generated */ 353 if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas))) 354 continue; 355 /* check reservations */ 356 changelock = 357 lockflag < 0 ? 358 rmlock(targetdelta) 359 : lockflag == 0 ? 360 0 361 : 362 addlock(targetdelta); 363 364 if ( 365 changelock < 0 || 366 changelock && !checkaccesslist() || 367 !dorewrite(lockflag, changelock) 368 ) 369 continue; 370 371 if (0 <= expmode) 372 Expand = expmode; 373 if (0 < lockflag && Expand == VAL_EXPAND) { 374 error("cannot combine -kv and -l"); 375 continue; 376 } 377 378 if (join && !preparejoin()) continue; 379 380 diagnose("revision %s%s\n",targetdelta->num, 381 0<lockflag ? " (locked)" : 382 lockflag<0 ? " (unlocked)" : ""); 383 384 /* Prepare to remove old working file if necessary. */ 385 if (workstatstat == 0) 386 if (!rmworkfile()) continue; 387 388 /* skip description */ 389 getdesc(false); /* don't echo*/ 390 391 locker_expansion = 0 < lockflag; 392 joinfilename = buildrevision( 393 gendeltas, targetdelta, 394 join&&tostdout ? (FILE*)0 : neworkptr, 395 Expand!=OLD_EXPAND 396 ); 397# if !large_memory 398 if (fcopy == neworkptr) 399 fcopy = 0; /* Don't close it twice. */ 400# endif 401 if_advise_access(changelock && gendeltas->first!=targetdelta, 402 finptr, MADV_SEQUENTIAL 403 ); 404 405 if (!donerewrite(changelock)) 406 continue; 407 408 newdate = targetdelta->date; 409 if (join) { 410 newdate = 0; 411 if (!joinfilename) { 412 aflush(neworkptr); 413 joinfilename = neworkfilename; 414 } 415 if (!buildjoin(joinfilename)) 416 continue; 417 } 418 } 419 if (!tostdout) { 420 r = 0; 421 if (mtimeflag && newdate) { 422 if (!join) 423 aflush(neworkptr); 424 r = setfiledate(neworkfilename, newdate); 425 } 426 if (r == 0) { 427 ignoreints(); 428 r = chnamemod(&neworkptr, neworkfilename, workfilename, 429 WORKMODE(RCSstat.st_mode, 430 !(Expand==VAL_EXPAND || lockflag<=0&&StrictLocks) 431 ) 432 ); 433 keepdirtemp(neworkfilename); 434 restoreints(); 435 } 436 if (r != 0) { 437 eerror(workfilename); 438 error("see %s", neworkfilename); 439 continue; 440 } 441 diagnose("done\n"); 442 } 443 } while (cleanup(), 444 ++argv, --argc >=1); 445 446 tempunlink(); 447 Ofclose(workstdout); 448 exitmain(exitstatus); 449 450} /* end of main (co) */ 451 452 static void 453cleanup() 454{ 455 if (nerror) exitstatus = EXIT_FAILURE; 456 Izclose(&finptr); 457 Ozclose(&frewrite); 458# if !large_memory 459 if (fcopy!=workstdout) Ozclose(&fcopy); 460# endif 461 if (neworkptr!=workstdout) Ozclose(&neworkptr); 462 dirtempunlink(); 463} 464 465#if lint 466# define exiterr coExit 467#endif 468 exiting void 469exiterr() 470{ 471 dirtempunlink(); 472 tempunlink(); 473 _exit(EXIT_FAILURE); 474} 475 476 477/***************************************************************** 478 * The following routines are auxiliary routines 479 *****************************************************************/ 480 481 static int 482rmworkfile() 483/* Function: prepares to remove workfilename, if it exists, and if 484 * it is read-only. 485 * Otherwise (file writable): 486 * if !quietmode asks the user whether to really delete it (default: fail); 487 * otherwise failure. 488 * Returns true if permission is gotten. 489 */ 490{ 491 if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) { 492 /* File is writable */ 493 if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ", 494 workfilename, 495 myself(workstat.st_uid) ? "" : ", and you do not own it" 496 )) { 497 error(!quietflag && ttystdin() 498 ? "checkout aborted" 499 : "writable %s exists; checkout aborted", workfilename); 500 return false; 501 } 502 } 503 /* Actual unlink is done later by caller. */ 504 return true; 505} 506 507 508 static int 509rmlock(delta) 510 struct hshentry const *delta; 511/* Function: removes the lock held by caller on delta. 512 * Returns -1 if someone else holds the lock, 513 * 0 if there is no lock on delta, 514 * and 1 if a lock was found and removed. 515 */ 516{ register struct lock * next, * trail; 517 char const *num; 518 struct lock dummy; 519 int whomatch, nummatch; 520 521 num=delta->num; 522 dummy.nextlock=next=Locks; 523 trail = &dummy; 524 while (next!=nil) { 525 whomatch = strcmp(getcaller(), next->login); 526 nummatch=strcmp(num,next->delta->num); 527 if ((whomatch==0) && (nummatch==0)) break; 528 /*found a lock on delta by caller*/ 529 if ((whomatch!=0)&&(nummatch==0)) { 530 error("revision %s locked by %s; use co -r or rcs -u",num,next->login); 531 return -1; 532 } 533 trail=next; 534 next=next->nextlock; 535 } 536 if (next!=nil) { 537 /*found one; delete it */ 538 trail->nextlock=next->nextlock; 539 Locks=dummy.nextlock; 540 next->delta->lockedby=nil; /* reset locked-by */ 541 return 1; /*success*/ 542 } else return 0; /*no lock on delta*/ 543} 544 545 546 547 548/***************************************************************** 549 * The rest of the routines are for handling joins 550 *****************************************************************/ 551 552 553 static char const * 554addjoin(joinrev) 555 char *joinrev; 556/* Add joinrev's number to joinlist, yielding address of char past joinrev, 557 * or nil if no such revision exists. 558 */ 559{ 560 register char *j; 561 register struct hshentry const *d; 562 char terminator; 563 struct buf numrev; 564 struct hshentries *joindeltas; 565 566 j = joinrev; 567 for (;;) { 568 switch (*j++) { 569 default: 570 continue; 571 case 0: 572 case ' ': case '\t': case '\n': 573 case ':': case ',': case ';': 574 break; 575 } 576 break; 577 } 578 terminator = *--j; 579 *j = 0; 580 bufautobegin(&numrev); 581 d = 0; 582 if (expandsym(joinrev, &numrev)) 583 d = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&joindeltas); 584 bufautoend(&numrev); 585 *j = terminator; 586 if (d) { 587 joinlist[++lastjoin] = d->num; 588 return j; 589 } 590 return nil; 591} 592 593 static int 594preparejoin() 595/* Function: Parses a join list pointed to by join and places pointers to the 596 * revision numbers into joinlist. 597 */ 598{ 599 register char const *j; 600 601 j=join; 602 lastjoin= -1; 603 for (;;) { 604 while ((*j==' ')||(*j=='\t')||(*j==',')) j++; 605 if (*j=='\0') break; 606 if (lastjoin>=joinlength-2) { 607 error("too many joins"); 608 return(false); 609 } 610 if (!(j = addjoin(j))) return false; 611 while ((*j==' ') || (*j=='\t')) j++; 612 if (*j == ':') { 613 j++; 614 while((*j==' ') || (*j=='\t')) j++; 615 if (*j!='\0') { 616 if (!(j = addjoin(j))) return false; 617 } else { 618 error("join pair incomplete"); 619 return false; 620 } 621 } else { 622 if (lastjoin==0) { /* first pair */ 623 /* common ancestor missing */ 624 joinlist[1]=joinlist[0]; 625 lastjoin=1; 626 /*derive common ancestor*/ 627 if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1]))) 628 return false; 629 } else { 630 error("join pair incomplete"); 631 return false; 632 } 633 } 634 } 635 if (lastjoin<1) { 636 error("empty join"); 637 return false; 638 } else return true; 639} 640 641 642 643 static char const * 644getancestor(r1, r2) 645 char const *r1, *r2; 646/* Yield the common ancestor of r1 and r2 if successful, nil otherwise. 647 * Work reliably only if r1 and r2 are not branch numbers. 648 */ 649{ 650 static struct buf t1, t2; 651 652 unsigned l1, l2, l3; 653 char const *r; 654 655 l1 = countnumflds(r1); 656 l2 = countnumflds(r2); 657 if ((2<l1 || 2<l2) && cmpnum(r1,r2)!=0) { 658 /* not on main trunk or identical */ 659 l3 = 0; 660 while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0) 661 l3 += 2; 662 /* This will terminate since r1 and r2 are not the same; see above. */ 663 if (l3==0) { 664 /* no common prefix; common ancestor on main trunk */ 665 VOID partialno(&t1, r1, l1>2 ? (unsigned)2 : l1); 666 VOID partialno(&t2, r2, l2>2 ? (unsigned)2 : l2); 667 r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string; 668 if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0) 669 return r; 670 } else if (cmpnumfld(r1, r2, l3+1)!=0) 671 return partialno(&t1,r1,l3); 672 } 673 error("common ancestor of %s and %s undefined", r1, r2); 674 return nil; 675} 676 677 678 679 static int 680buildjoin(initialfile) 681 char const *initialfile; 682/* Function: merge pairs of elements in joinlist into initialfile 683 * If workstdout is set, copy result to stdout. 684 * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink(). 685 */ 686{ 687 struct buf commarg; 688 struct buf subs; 689 char const *rev2, *rev3; 690 int i; 691 char const *cov[10], *mergev[12]; 692 char const **p; 693 694 bufautobegin(&commarg); 695 bufautobegin(&subs); 696 rev2 = maketemp(0); 697 rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */ 698 699 cov[0] = nil; 700 /* cov[1] setup below */ 701 cov[2] = CO; 702 /* cov[3] setup below */ 703 p = &cov[4]; 704 if (expandarg) *p++ = expandarg; 705 if (suffixarg) *p++ = suffixarg; 706 if (versionarg) *p++ = versionarg; 707 *p++ = quietarg; 708 *p++ = RCSfilename; 709 *p = nil; 710 711 mergev[0] = nil; 712 mergev[1] = nil; 713 mergev[2] = MERGE; 714 mergev[3] = mergev[5] = "-L"; 715 /* rest of mergev setup below */ 716 717 i=0; 718 while (i<lastjoin) { 719 /*prepare marker for merge*/ 720 if (i==0) 721 bufscpy(&subs, targetdelta->num); 722 else { 723 bufscat(&subs, ","); 724 bufscat(&subs, joinlist[i-2]); 725 bufscat(&subs, ":"); 726 bufscat(&subs, joinlist[i-1]); 727 } 728 diagnose("revision %s\n",joinlist[i]); 729 bufscpy(&commarg, "-p"); 730 bufscat(&commarg, joinlist[i]); 731 cov[1] = rev2; 732 cov[3] = commarg.string; 733 if (runv(cov)) 734 goto badmerge; 735 diagnose("revision %s\n",joinlist[i+1]); 736 bufscpy(&commarg, "-p"); 737 bufscat(&commarg, joinlist[i+1]); 738 cov[1] = rev3; 739 cov[3] = commarg.string; 740 if (runv(cov)) 741 goto badmerge; 742 diagnose("merging...\n"); 743 mergev[4] = subs.string; 744 mergev[6] = joinlist[i+1]; 745 p = &mergev[7]; 746 if (quietflag) *p++ = quietarg; 747 if (lastjoin<=i+2 && workstdout) *p++ = "-p"; 748 *p++ = initialfile; 749 *p++ = rev2; 750 *p++ = rev3; 751 *p = nil; 752 switch (runv(mergev)) { 753 case DIFF_FAILURE: case DIFF_SUCCESS: 754 break; 755 default: 756 goto badmerge; 757 } 758 i=i+2; 759 } 760 bufautoend(&commarg); 761 bufautoend(&subs); 762 return true; 763 764 badmerge: 765 nerror++; 766 bufautoend(&commarg); 767 bufautoend(&subs); 768 return false; 769} 770