1 /* Check in revisions of RCS files from working files. */ 2 3 /* Copyright 1982, 1988, 1989 Walter Tichy 4 Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert 5 Distributed under license by the Free Software Foundation, Inc. 6 7 This file is part of RCS. 8 9 RCS is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 2, or (at your option) 12 any later version. 13 14 RCS is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with RCS; see the file COPYING. 21 If not, write to the Free Software Foundation, 22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 24 Report problems and direct all questions to: 25 26 rcs-bugs@cs.purdue.edu 27 28 */ 29 30 /* 31 * $FreeBSD: src/gnu/usr.bin/rcs/ci/ci.c,v 1.7 1999/08/27 23:36:38 peter Exp $ 32 * $DragonFly: src/gnu/usr.bin/rcs/ci/ci.c,v 1.2 2003/06/17 04:25:47 dillon Exp $ 33 * 34 * Revision 5.30 1995/06/16 06:19:24 eggert 35 * Update FSF address. 36 * 37 * Revision 5.29 1995/06/01 16:23:43 eggert 38 * (main): Add -kb. 39 * Use `cmpdate', not `cmpnum', to compare dates. 40 * This is for MKS RCS's incompatible 20th-century date format. 41 * Don't worry about errno after ftruncate fails. 42 * Fix input file rewinding bug when large_memory && !maps_memory 43 * and checking in a branch tip. 44 * 45 * (fixwork): Fall back on chmod if fchmod fails, since it might be ENOSYS. 46 * 47 * Revision 5.28 1994/03/20 04:52:58 eggert 48 * Do not generate a corrupted RCS file if the user modifies the working file 49 * while `ci' is running. 50 * Do not remove the lock when `ci -l' reverts. 51 * Move buffer-flushes out of critical sections, since they aren't critical. 52 * Use ORCSerror to clean up after a fatal error. 53 * Specify subprocess input via file descriptor, not file name. 54 * 55 * Revision 5.27 1993/11/09 17:40:15 eggert 56 * -V now prints version on stdout and exits. Don't print usage twice. 57 * 58 * Revision 5.26 1993/11/03 17:42:27 eggert 59 * Add -z. Don't subtract from RCS file timestamp even if -T. 60 * Scan for and use Name keyword if -k. 61 * Don't discard ignored phrases. Improve quality of diagnostics. 62 * 63 * Revision 5.25 1992/07/28 16:12:44 eggert 64 * Add -i, -j, -V. Check that working and RCS files are distinct. 65 * 66 * Revision 5.24 1992/02/17 23:02:06 eggert 67 * `-rREV' now just specifies a revision REV; only bare `-r' reverts to default. 68 * Add -T. 69 * 70 * Revision 5.23 1992/01/27 16:42:51 eggert 71 * Always unlock branchpoint if caller has a lock. 72 * Add support for bad_chmod_close, bad_creat0. lint -> RCS_lint 73 * 74 * Revision 5.22 1992/01/06 02:42:34 eggert 75 * Invoke utime() before chmod() to keep some buggy systems happy. 76 * 77 * Revision 5.21 1991/11/20 17:58:07 eggert 78 * Don't read the delta tree from a nonexistent RCS file. 79 * 80 * Revision 5.20 1991/10/07 17:32:46 eggert 81 * Fix log bugs. Remove lint. 82 * 83 * Revision 5.19 1991/09/26 23:10:30 eggert 84 * Plug file descriptor leak. 85 * 86 * Revision 5.18 1991/09/18 07:29:10 eggert 87 * Work around a common ftruncate() bug. 88 * 89 * Revision 5.17 1991/09/10 22:15:46 eggert 90 * Fix test for redirected stdin. 91 * 92 * Revision 5.16 1991/08/19 23:17:54 eggert 93 * When there are no changes, revert to previous revision instead of aborting. 94 * Add piece tables, -M, -r$. Tune. 95 * 96 * Revision 5.15 1991/04/21 11:58:14 eggert 97 * Ensure that working file is newer than RCS file after ci -[lu]. 98 * Add -x, RCSINIT, MS-DOS support. 99 * 100 * Revision 5.14 1991/02/28 19:18:47 eggert 101 * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first. 102 * Fix ci -ko -l mode bug. Open work file at most once. 103 * 104 * Revision 5.13 1991/02/25 07:12:33 eggert 105 * getdate -> getcurdate (SVR4 name clash) 106 * 107 * Revision 5.12 1990/12/31 01:00:12 eggert 108 * Don't use uninitialized storage when handling -{N,n}. 109 * 110 * Revision 5.11 1990/12/04 05:18:36 eggert 111 * Use -I for prompts and -q for diagnostics. 112 * 113 * Revision 5.10 1990/11/05 20:30:10 eggert 114 * Don't remove working file when aborting due to no changes. 115 * 116 * Revision 5.9 1990/11/01 05:03:23 eggert 117 * Add -I and new -t behavior. Permit arbitrary data in logs. 118 * 119 * Revision 5.8 1990/10/04 06:30:09 eggert 120 * Accumulate exit status across files. 121 * 122 * Revision 5.7 1990/09/25 20:11:46 hammer 123 * fixed another small typo 124 * 125 * Revision 5.6 1990/09/24 21:48:50 hammer 126 * added cleanups from Paul Eggert. 127 * 128 * Revision 5.5 1990/09/21 06:16:38 hammer 129 * made it handle multiple -{N,n}'s. Also, made it treat re-directed stdin 130 * the same as the terminal 131 * 132 * Revision 5.4 1990/09/20 02:38:51 eggert 133 * ci -k now checks dates more thoroughly. 134 * 135 * Revision 5.3 1990/09/11 02:41:07 eggert 136 * Fix revision bug with `ci -k file1 file2'. 137 * 138 * Revision 5.2 1990/09/04 08:02:10 eggert 139 * Permit adjacent revisions with identical time stamps (possible on fast hosts). 140 * Improve incomplete line handling. Standardize yes-or-no procedure. 141 * 142 * Revision 5.1 1990/08/29 07:13:44 eggert 143 * Expand locker value like co. Clean old log messages too. 144 * 145 * Revision 5.0 1990/08/22 08:10:00 eggert 146 * Don't require a final newline. 147 * Make lock and temp files faster and safer. 148 * Remove compile-time limits; use malloc instead. 149 * Permit dates past 1999/12/31. Switch to GMT. 150 * Add setuid support. Don't pass +args to diff. Check diff's output. 151 * Ansify and Posixate. Add -k, -V. Remove snooping. Tune. 152 * Check diff's output. 153 * 154 * Revision 4.9 89/05/01 15:10:54 narten 155 * changed copyright header to reflect current distribution rules 156 * 157 * Revision 4.8 88/11/08 13:38:23 narten 158 * changes from root@seismo.CSS.GOV (Super User) 159 * -d with no arguments uses the mod time of the file it is checking in 160 * 161 * Revision 4.7 88/08/09 19:12:07 eggert 162 * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one. 163 * Use execv(), not system(); allow cc -R; remove lint. 164 * isatty(fileno(stdin)) -> ttystdin() 165 * 166 * Revision 4.6 87/12/18 11:34:41 narten 167 * lint cleanups (from Guy Harris) 168 * 169 * Revision 4.5 87/10/18 10:18:48 narten 170 * Updating version numbers. Changes relative to revision 1.1 are actually 171 * relative to 4.3 172 * 173 * Revision 1.3 87/09/24 13:57:19 narten 174 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 175 * warnings) 176 * 177 * Revision 1.2 87/03/27 14:21:33 jenkins 178 * Port to suns 179 * 180 * Revision 4.3 83/12/15 12:28:54 wft 181 * ci -u and ci -l now set mode of working file properly. 182 * 183 * Revision 4.2 83/12/05 13:40:54 wft 184 * Merged with 3.9.1.1: added calls to clearerr(stdin). 185 * made rewriteflag external. 186 * 187 * Revision 4.1 83/05/10 17:03:06 wft 188 * Added option -d and -w, and updated assingment of date, etc. to new delta. 189 * Added handling of default branches. 190 * Option -k generates std. log message; fixed undef. pointer in reading of log. 191 * Replaced getlock() with findlock(), link--unlink with rename(), 192 * getpwuid() with getcaller(). 193 * Moved all revision number generation to new routine addelta(). 194 * Removed calls to stat(); now done by pairfilenames(). 195 * Changed most calls to catchints() with restoreints(). 196 * Directed all interactive messages to stderr. 197 * 198 * Revision 3.9.1.1 83/10/19 04:21:03 lepreau 199 * Added clearerr(stdin) to getlogmsg() for re-reading stdin. 200 * 201 * Revision 3.9 83/02/15 15:25:44 wft 202 * 4.2 prerelease 203 * 204 * Revision 3.9 83/02/15 15:25:44 wft 205 * Added call to fastcopy() to copy remainder of RCS file. 206 * 207 * Revision 3.8 83/01/14 15:34:05 wft 208 * Added ignoring of interrupts while new RCS file is renamed; 209 * Avoids deletion of RCS files by interrupts. 210 * 211 * Revision 3.7 82/12/10 16:09:20 wft 212 * Corrected checking of return code from diff. 213 * 214 * Revision 3.6 82/12/08 21:34:49 wft 215 * Using DATEFORM to prepare date of checked-in revision; 216 * Fixed return from addbranch(). 217 * 218 * Revision 3.5 82/12/04 18:32:42 wft 219 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated 220 * field lockedby in removelock(), moved getlogmsg() before calling diff. 221 * 222 * Revision 3.4 82/12/02 13:27:13 wft 223 * added option -k. 224 * 225 * Revision 3.3 82/11/28 20:53:31 wft 226 * Added mustcheckin() to check for redundant checkins. 227 * Added xpandfile() to do keyword expansion for -u and -l; 228 * -m appends linefeed to log message if necessary. 229 * getlogmsg() suppresses prompt if stdin is not a terminal. 230 * Replaced keeplock with lockflag, fclose() with ffclose(), 231 * %02d with %.2d, getlogin() with getpwuid(). 232 * 233 * Revision 3.2 82/10/18 20:57:23 wft 234 * An RCS file inherits its mode during the first ci from the working file, 235 * otherwise it stays the same, except that write permission is removed. 236 * Fixed ci -l, added ci -u (both do an implicit co after the ci). 237 * Fixed call to getlogin(), added call to getfullRCSname(), added check 238 * for write error. 239 * Changed conflicting identifiers. 240 * 241 * Revision 3.1 82/10/13 16:04:59 wft 242 * fixed type of variables receiving from getc() (char -> int). 243 * added include file dbm.h for getting BYTESIZ. This is used 244 * to check the return code from diff portably. 245 */ 246 247 #include "rcsbase.h" 248 249 struct Symrev { 250 char const *ssymbol; 251 int override; 252 struct Symrev * nextsym; 253 }; 254 255 static char const *getcurdate P((void)); 256 static int addbranch P((struct hshentry*,struct buf*,int)); 257 static int addelta P((void)); 258 static int addsyms P((char const*)); 259 static int fixwork P((mode_t,time_t)); 260 static int removelock P((struct hshentry*)); 261 static int xpandfile P((RILE*,struct hshentry const*,char const**,int)); 262 static struct cbuf getlogmsg P((void)); 263 static void cleanup P((void)); 264 static void incnum P((char const*,struct buf*)); 265 static void addassoclst P((int,char const*)); 266 267 static FILE *exfile; 268 static RILE *workptr; /* working file pointer */ 269 static struct buf newdelnum; /* new revision number */ 270 static struct cbuf msg; 271 static int exitstatus; 272 static int forceciflag; /* forces check in */ 273 static int keepflag, keepworkingfile, rcsinitflag; 274 static struct hshentries *gendeltas; /* deltas to be generated */ 275 static struct hshentry *targetdelta; /* old delta to be generated */ 276 static struct hshentry newdelta; /* new delta to be inserted */ 277 static struct stat workstat; 278 static struct Symrev *assoclst, **nextassoc; 279 280 mainProg(ciId, "ci", "$DragonFly: src/gnu/usr.bin/rcs/ci/ci.c,v 1.2 2003/06/17 04:25:47 dillon Exp $") 281 { 282 static char const cmdusage[] = 283 "\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -T -Vn -wwho -xsuff -zzone file ..."; 284 static char const default_state[] = DEFAULTSTATE; 285 286 char altdate[datesize]; 287 char olddate[datesize]; 288 char newdatebuf[datesize + zonelenmax]; 289 char targetdatebuf[datesize + zonelenmax]; 290 char *a, **newargv, *textfile; 291 char const *author, *krev, *rev, *state; 292 char const *diffname, *expname; 293 char const *newworkname; 294 int initflag, mustread; 295 int lockflag, lockthis, mtimeflag, removedlock, Ttimeflag; 296 int r; 297 int changedRCS, changework, dolog, newhead; 298 int usestatdate; /* Use mod time of file for -d. */ 299 mode_t newworkmode; /* mode for working file */ 300 time_t mtime, wtime; 301 struct hshentry *workdelta; 302 303 setrid(); 304 305 author = rev = state = textfile = 0; 306 initflag = lockflag = mustread = false; 307 mtimeflag = false; 308 Ttimeflag = false; 309 altdate[0]= '\0'; /* empty alternate date for -d */ 310 usestatdate=false; 311 suffixes = X_DEFAULT; 312 nextassoc = &assoclst; 313 314 argc = getRCSINIT(argc, argv, &newargv); 315 argv = newargv; 316 while (a = *++argv, 0<--argc && *a++=='-') { 317 switch (*a++) { 318 319 case 'r': 320 if (*a) 321 goto revno; 322 keepworkingfile = lockflag = false; 323 break; 324 325 case 'l': 326 keepworkingfile = lockflag = true; 327 revno: 328 if (*a) { 329 if (rev) warn("redefinition of revision number"); 330 rev = a; 331 } 332 break; 333 334 case 'u': 335 keepworkingfile=true; lockflag=false; 336 goto revno; 337 338 case 'i': 339 initflag = true; 340 goto revno; 341 342 case 'j': 343 mustread = true; 344 goto revno; 345 346 case 'I': 347 interactiveflag = true; 348 goto revno; 349 350 case 'q': 351 quietflag=true; 352 goto revno; 353 354 case 'f': 355 forceciflag=true; 356 goto revno; 357 358 case 'k': 359 keepflag=true; 360 goto revno; 361 362 case 'm': 363 if (msg.size) redefined('m'); 364 msg = cleanlogmsg(a, strlen(a)); 365 if (!msg.size) 366 error("missing message for -m option"); 367 break; 368 369 case 'n': 370 if (!*a) { 371 error("missing symbolic name after -n"); 372 break; 373 } 374 checkssym(a); 375 addassoclst(false, a); 376 break; 377 378 case 'N': 379 if (!*a) { 380 error("missing symbolic name after -N"); 381 break; 382 } 383 checkssym(a); 384 addassoclst(true, a); 385 break; 386 387 case 's': 388 if (*a) { 389 if (state) redefined('s'); 390 checksid(a); 391 state = a; 392 } else 393 error("missing state for -s option"); 394 break; 395 396 case 't': 397 if (*a) { 398 if (textfile) redefined('t'); 399 textfile = a; 400 } 401 break; 402 403 case 'd': 404 if (altdate[0] || usestatdate) 405 redefined('d'); 406 altdate[0] = '\0'; 407 if (!(usestatdate = !*a)) 408 str2date(a, altdate); 409 break; 410 411 case 'M': 412 mtimeflag = true; 413 goto revno; 414 415 case 'w': 416 if (*a) { 417 if (author) redefined('w'); 418 checksid(a); 419 author = a; 420 } else 421 error("missing author for -w option"); 422 break; 423 424 case 'x': 425 suffixes = a; 426 break; 427 428 case 'V': 429 setRCSversion(*argv); 430 break; 431 432 case 'z': 433 zone_set(a); 434 break; 435 436 case 'T': 437 if (!*a) { 438 Ttimeflag = true; 439 break; 440 } 441 /* fall into */ 442 default: 443 error("unknown option: %s%s", *argv, cmdusage); 444 }; 445 } /* end processing of options */ 446 447 /* Handle all pathnames. */ 448 if (nerror) cleanup(); 449 else if (argc < 1) faterror("no input file%s", cmdusage); 450 else for (; 0 < argc; cleanup(), ++argv, --argc) { 451 targetdelta = 0; 452 ffree(); 453 454 switch (pairnames(argc, argv, rcswriteopen, mustread, false)) { 455 456 case -1: /* New RCS file */ 457 # if has_setuid && has_getuid 458 if (euid() != ruid()) { 459 workerror("setuid initial checkin prohibited; use `rcs -i -a' first"); 460 continue; 461 } 462 # endif 463 rcsinitflag = true; 464 break; 465 466 case 0: /* Error */ 467 continue; 468 469 case 1: /* Normal checkin with prev . RCS file */ 470 if (initflag) { 471 rcserror("already exists"); 472 continue; 473 } 474 rcsinitflag = !Head; 475 } 476 477 /* 478 * RCSname contains the name of the RCS file, and 479 * workname contains the name of the working file. 480 * If the RCS file exists, finptr contains the file descriptor for the 481 * RCS file, and RCSstat is set. The admin node is initialized. 482 */ 483 484 diagnose("%s <-- %s\n", RCSname, workname); 485 486 if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) { 487 eerror(workname); 488 continue; 489 } 490 491 if (finptr) { 492 if (same_file(RCSstat, workstat, 0)) { 493 rcserror("RCS file is the same as working file %s.", 494 workname 495 ); 496 continue; 497 } 498 if (!checkaccesslist()) 499 continue; 500 } 501 502 krev = rev; 503 if (keepflag) { 504 /* get keyword values from working file */ 505 if (!getoldkeys(workptr)) continue; 506 if (!rev && !*(krev = prevrev.string)) { 507 workerror("can't find a revision number"); 508 continue; 509 } 510 if (!*prevdate.string && *altdate=='\0' && usestatdate==false) 511 workwarn("can't find a date"); 512 if (!*prevauthor.string && !author) 513 workwarn("can't find an author"); 514 if (!*prevstate.string && !state) 515 workwarn("can't find a state"); 516 } /* end processing keepflag */ 517 518 /* Read the delta tree. */ 519 if (finptr) 520 gettree(); 521 522 /* expand symbolic revision number */ 523 if (!fexpandsym(krev, &newdelnum, workptr)) 524 continue; 525 526 /* splice new delta into tree */ 527 if ((removedlock = addelta()) < 0) 528 continue; 529 530 newdelta.num = newdelnum.string; 531 newdelta.branches = 0; 532 newdelta.lockedby = 0; /* This might be changed by addlock(). */ 533 newdelta.selector = true; 534 newdelta.name = 0; 535 clear_buf(&newdelta.ig); 536 clear_buf(&newdelta.igtext); 537 /* set author */ 538 if (author) 539 newdelta.author=author; /* set author given by -w */ 540 else if (keepflag && *prevauthor.string) 541 newdelta.author=prevauthor.string; /* preserve old author if possible*/ 542 else newdelta.author=getcaller();/* otherwise use caller's id */ 543 newdelta.state = default_state; 544 if (state) 545 newdelta.state=state; /* set state given by -s */ 546 else if (keepflag && *prevstate.string) 547 newdelta.state=prevstate.string; /* preserve old state if possible */ 548 if (usestatdate) { 549 time2date(workstat.st_mtime, altdate); 550 } 551 if (*altdate!='\0') 552 newdelta.date=altdate; /* set date given by -d */ 553 else if (keepflag && *prevdate.string) { 554 /* Preserve old date if possible. */ 555 str2date(prevdate.string, olddate); 556 newdelta.date = olddate; 557 } else 558 newdelta.date = getcurdate(); /* use current date */ 559 /* now check validity of date -- needed because of -d and -k */ 560 if (targetdelta && 561 cmpdate(newdelta.date,targetdelta->date) < 0) { 562 rcserror("Date %s precedes %s in revision %s.", 563 date2str(newdelta.date, newdatebuf), 564 date2str(targetdelta->date, targetdatebuf), 565 targetdelta->num 566 ); 567 continue; 568 } 569 570 571 if (lockflag && addlock(&newdelta, true) < 0) continue; 572 573 if (keepflag && *prevname.string) 574 if (addsymbol(newdelta.num, prevname.string, false) < 0) 575 continue; 576 if (!addsyms(newdelta.num)) 577 continue; 578 579 580 putadmin(); 581 puttree(Head,frewrite); 582 putdesc(false,textfile); 583 584 changework = Expand < MIN_UNCHANGED_EXPAND; 585 dolog = true; 586 lockthis = lockflag; 587 workdelta = &newdelta; 588 589 /* build rest of file */ 590 if (rcsinitflag) { 591 diagnose("initial revision: %s\n", newdelta.num); 592 /* get logmessage */ 593 newdelta.log=getlogmsg(); 594 putdftext(&newdelta, workptr, frewrite, false); 595 RCSstat.st_mode = workstat.st_mode; 596 RCSstat.st_nlink = 0; 597 changedRCS = true; 598 } else { 599 diffname = maketemp(0); 600 newhead = Head == &newdelta; 601 if (!newhead) 602 foutptr = frewrite; 603 expname = buildrevision( 604 gendeltas, targetdelta, (FILE*)0, false 605 ); 606 if ( 607 !forceciflag && 608 strcmp(newdelta.state, targetdelta->state) == 0 && 609 (changework = rcsfcmp( 610 workptr, &workstat, expname, targetdelta 611 )) <= 0 612 ) { 613 diagnose("file is unchanged; reverting to previous revision %s\n", 614 targetdelta->num 615 ); 616 if (removedlock < lockflag) { 617 diagnose("previous revision was not locked; ignoring -l option\n"); 618 lockthis = 0; 619 } 620 dolog = false; 621 if (! (changedRCS = lockflag<removedlock || assoclst)) 622 workdelta = targetdelta; 623 else { 624 /* 625 * We have started to build the wrong new RCS file. 626 * Start over from the beginning. 627 */ 628 long hwm = ftell(frewrite); 629 int bad_truncate; 630 Orewind(frewrite); 631 632 /* 633 * Work around a common ftruncate() bug: 634 * NFS won't let you truncate a file that you 635 * currently lack permissions for, even if you 636 * had permissions when you opened it. 637 * Also, Posix 1003.1b-1993 sec 5.6.7.2 p 128 l 1022 638 * says ftruncate might fail because it's not supported. 639 */ 640 # if !has_ftruncate 641 # undef ftruncate 642 # define ftruncate(fd,length) (-1) 643 # endif 644 bad_truncate = ftruncate(fileno(frewrite), (off_t)0); 645 646 Irewind(finptr); 647 Lexinit(); 648 getadmin(); 649 gettree(); 650 if (!(workdelta = genrevs( 651 targetdelta->num, (char*)0, (char*)0, (char*)0, 652 &gendeltas 653 ))) 654 continue; 655 workdelta->log = targetdelta->log; 656 if (newdelta.state != default_state) 657 workdelta->state = newdelta.state; 658 if (lockthis<removedlock && removelock(workdelta)<0) 659 continue; 660 if (!addsyms(workdelta->num)) 661 continue; 662 if (dorewrite(true, true) != 0) 663 continue; 664 fastcopy(finptr, frewrite); 665 if (bad_truncate) 666 while (ftell(frewrite) < hwm) 667 /* White out any earlier mistake with '\n's. */ 668 /* This is unlikely. */ 669 afputc('\n', frewrite); 670 } 671 } else { 672 int wfd = Ifileno(workptr); 673 struct stat checkworkstat; 674 char const *diffv[6 + !!OPEN_O_BINARY], **diffp; 675 # if large_memory && !maps_memory 676 FILE *wfile = workptr->stream; 677 long wfile_off; 678 # endif 679 # if !has_fflush_input && !(large_memory && maps_memory) 680 off_t wfd_off; 681 # endif 682 683 diagnose("new revision: %s; previous revision: %s\n", 684 newdelta.num, targetdelta->num 685 ); 686 newdelta.log = getlogmsg(); 687 # if !large_memory 688 Irewind(workptr); 689 # if has_fflush_input 690 if (fflush(workptr) != 0) 691 Ierror(); 692 # endif 693 # else 694 # if !maps_memory 695 if ( 696 (wfile_off = ftell(wfile)) == -1 697 || fseek(wfile, 0L, SEEK_SET) != 0 698 # if has_fflush_input 699 || fflush(wfile) != 0 700 # endif 701 ) 702 Ierror(); 703 # endif 704 # endif 705 # if !has_fflush_input && !(large_memory && maps_memory) 706 wfd_off = lseek(wfd, (off_t)0, SEEK_CUR); 707 if (wfd_off == -1 708 || (wfd_off != 0 709 && lseek(wfd, (off_t)0, SEEK_SET) != 0)) 710 Ierror(); 711 # endif 712 diffp = diffv; 713 *++diffp = DIFF; 714 *++diffp = DIFFFLAGS; 715 # if OPEN_O_BINARY 716 if (Expand == BINARY_EXPAND) 717 *++diffp = "--binary"; 718 # endif 719 *++diffp = newhead ? "-" : expname; 720 *++diffp = newhead ? expname : "-"; 721 *++diffp = 0; 722 switch (runv(wfd, diffname, diffv)) { 723 case DIFF_FAILURE: case DIFF_SUCCESS: break; 724 default: rcsfaterror("diff failed"); 725 } 726 # if !has_fflush_input && !(large_memory && maps_memory) 727 if (lseek(wfd, wfd_off, SEEK_CUR) == -1) 728 Ierror(); 729 # endif 730 # if large_memory && !maps_memory 731 if (fseek(wfile, wfile_off, SEEK_SET) != 0) 732 Ierror(); 733 # endif 734 if (newhead) { 735 Irewind(workptr); 736 putdftext(&newdelta, workptr, frewrite, false); 737 if (!putdtext(targetdelta,diffname,frewrite,true)) continue; 738 } else 739 if (!putdtext(&newdelta,diffname,frewrite,true)) continue; 740 741 /* 742 * Check whether the working file changed during checkin, 743 * to avoid producing an inconsistent RCS file. 744 */ 745 if ( 746 fstat(wfd, &checkworkstat) != 0 747 || workstat.st_mtime != checkworkstat.st_mtime 748 || workstat.st_size != checkworkstat.st_size 749 ) { 750 workerror("file changed during checkin"); 751 continue; 752 } 753 754 changedRCS = true; 755 } 756 } 757 758 /* Deduce time_t of new revision if it is needed later. */ 759 wtime = (time_t)-1; 760 if (mtimeflag | Ttimeflag) 761 wtime = date2time(workdelta->date); 762 763 if (donerewrite(changedRCS, 764 !Ttimeflag ? (time_t)-1 765 : finptr && wtime < RCSstat.st_mtime ? RCSstat.st_mtime 766 : wtime 767 ) != 0) 768 continue; 769 770 if (!keepworkingfile) { 771 Izclose(&workptr); 772 r = un_link(workname); /* Get rid of old file */ 773 } else { 774 newworkmode = WORKMODE(RCSstat.st_mode, 775 ! (Expand==VAL_EXPAND || lockthis < StrictLocks) 776 ); 777 mtime = mtimeflag ? wtime : (time_t)-1; 778 779 /* Expand if it might change or if we can't fix mode, time. */ 780 if (changework || (r=fixwork(newworkmode,mtime)) != 0) { 781 Irewind(workptr); 782 /* Expand keywords in file. */ 783 locker_expansion = lockthis; 784 workdelta->name = 785 namedrev( 786 assoclst ? assoclst->ssymbol 787 : keepflag && *prevname.string ? prevname.string 788 : rev, 789 workdelta 790 ); 791 switch (xpandfile( 792 workptr, workdelta, &newworkname, dolog 793 )) { 794 default: 795 continue; 796 797 case 0: 798 /* 799 * No expansion occurred; try to reuse working file 800 * unless we already tried and failed. 801 */ 802 if (changework) 803 if ((r=fixwork(newworkmode,mtime)) == 0) 804 break; 805 /* fall into */ 806 case 1: 807 Izclose(&workptr); 808 aflush(exfile); 809 ignoreints(); 810 r = chnamemod(&exfile, newworkname, 811 workname, 1, newworkmode, mtime 812 ); 813 keepdirtemp(newworkname); 814 restoreints(); 815 } 816 } 817 } 818 if (r != 0) { 819 eerror(workname); 820 continue; 821 } 822 diagnose("done\n"); 823 824 } 825 826 tempunlink(); 827 exitmain(exitstatus); 828 } /* end of main (ci) */ 829 830 static void 831 cleanup() 832 { 833 if (nerror) exitstatus = EXIT_FAILURE; 834 Izclose(&finptr); 835 Izclose(&workptr); 836 Ozclose(&exfile); 837 Ozclose(&fcopy); 838 ORCSclose(); 839 dirtempunlink(); 840 } 841 842 #if RCS_lint 843 # define exiterr ciExit 844 #endif 845 void 846 exiterr() 847 { 848 ORCSerror(); 849 dirtempunlink(); 850 tempunlink(); 851 _exit(EXIT_FAILURE); 852 } 853 854 /*****************************************************************/ 855 /* the rest are auxiliary routines */ 856 857 858 static int 859 addelta() 860 /* Function: Appends a delta to the delta tree, whose number is 861 * given by newdelnum. Updates Head, newdelnum, newdelnumlength, 862 * and the links in newdelta. 863 * Return -1 on error, 1 if a lock is removed, 0 otherwise. 864 */ 865 { 866 register char *tp; 867 register int i; 868 int removedlock; 869 int newdnumlength; /* actual length of new rev. num. */ 870 871 newdnumlength = countnumflds(newdelnum.string); 872 873 if (rcsinitflag) { 874 /* this covers non-existing RCS file and a file initialized with rcs -i */ 875 if (newdnumlength==0 && Dbranch) { 876 bufscpy(&newdelnum, Dbranch); 877 newdnumlength = countnumflds(Dbranch); 878 } 879 if (newdnumlength==0) bufscpy(&newdelnum, "1.1"); 880 else if (newdnumlength==1) bufscat(&newdelnum, ".1"); 881 else if (newdnumlength>2) { 882 rcserror("Branch point doesn't exist for revision %s.", 883 newdelnum.string 884 ); 885 return -1; 886 } /* newdnumlength == 2 is OK; */ 887 Head = &newdelta; 888 newdelta.next = 0; 889 return 0; 890 } 891 if (newdnumlength==0) { 892 /* derive new revision number from locks */ 893 switch (findlock(true, &targetdelta)) { 894 895 default: 896 /* found two or more old locks */ 897 return -1; 898 899 case 1: 900 /* found an old lock */ 901 /* check whether locked revision exists */ 902 if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas)) 903 return -1; 904 if (targetdelta==Head) { 905 /* make new head */ 906 newdelta.next=Head; 907 Head= &newdelta; 908 } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) { 909 /* new tip revision on side branch */ 910 targetdelta->next= &newdelta; 911 newdelta.next = 0; 912 } else { 913 /* middle revision; start a new branch */ 914 bufscpy(&newdelnum, ""); 915 return addbranch(targetdelta, &newdelnum, 1); 916 } 917 incnum(targetdelta->num, &newdelnum); 918 return 1; /* successful use of existing lock */ 919 920 case 0: 921 /* no existing lock; try Dbranch */ 922 /* update newdelnum */ 923 if (StrictLocks || !myself(RCSstat.st_uid)) { 924 rcserror("no lock set by %s", getcaller()); 925 return -1; 926 } 927 if (Dbranch) { 928 bufscpy(&newdelnum, Dbranch); 929 } else { 930 incnum(Head->num, &newdelnum); 931 } 932 newdnumlength = countnumflds(newdelnum.string); 933 /* now fall into next statement */ 934 } 935 } 936 if (newdnumlength<=2) { 937 /* add new head per given number */ 938 if(newdnumlength==1) { 939 /* make a two-field number out of it*/ 940 if (cmpnumfld(newdelnum.string,Head->num,1)==0) 941 incnum(Head->num, &newdelnum); 942 else 943 bufscat(&newdelnum, ".1"); 944 } 945 if (cmpnum(newdelnum.string,Head->num) <= 0) { 946 rcserror("revision %s too low; must be higher than %s", 947 newdelnum.string, Head->num 948 ); 949 return -1; 950 } 951 targetdelta = Head; 952 if (0 <= (removedlock = removelock(Head))) { 953 if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas)) 954 return -1; 955 newdelta.next = Head; 956 Head = &newdelta; 957 } 958 return removedlock; 959 } else { 960 /* put new revision on side branch */ 961 /*first, get branch point */ 962 tp = newdelnum.string; 963 for (i = newdnumlength - ((newdnumlength&1) ^ 1); --i; ) 964 while (*tp++ != '.') 965 continue; 966 *--tp = 0; /* Kill final dot to get old delta temporarily. */ 967 if (!(targetdelta=genrevs(newdelnum.string,(char*)0,(char*)0,(char*)0,&gendeltas))) 968 return -1; 969 if (cmpnum(targetdelta->num, newdelnum.string) != 0) { 970 rcserror("can't find branch point %s", newdelnum.string); 971 return -1; 972 } 973 *tp = '.'; /* Restore final dot. */ 974 return addbranch(targetdelta, &newdelnum, 0); 975 } 976 } 977 978 979 980 static int 981 addbranch(branchpoint, num, removedlock) 982 struct hshentry *branchpoint; 983 struct buf *num; 984 int removedlock; 985 /* adds a new branch and branch delta at branchpoint. 986 * If num is the null string, appends the new branch, incrementing 987 * the highest branch number (initially 1), and setting the level number to 1. 988 * the new delta and branchhead are in globals newdelta and newbranch, resp. 989 * the new number is placed into num. 990 * Return -1 on error, 1 if a lock is removed, 0 otherwise. 991 * If REMOVEDLOCK is 1, a lock was already removed. 992 */ 993 { 994 struct branchhead *bhead, **btrail; 995 struct buf branchnum; 996 int result; 997 int field, numlength; 998 static struct branchhead newbranch; /* new branch to be inserted */ 999 1000 numlength = countnumflds(num->string); 1001 1002 if (!branchpoint->branches) { 1003 /* start first branch */ 1004 branchpoint->branches = &newbranch; 1005 if (numlength==0) { 1006 bufscpy(num, branchpoint->num); 1007 bufscat(num, ".1.1"); 1008 } else if (numlength&1) 1009 bufscat(num, ".1"); 1010 newbranch.nextbranch = 0; 1011 1012 } else if (numlength==0) { 1013 /* append new branch to the end */ 1014 bhead=branchpoint->branches; 1015 while (bhead->nextbranch) bhead=bhead->nextbranch; 1016 bhead->nextbranch = &newbranch; 1017 bufautobegin(&branchnum); 1018 getbranchno(bhead->hsh->num, &branchnum); 1019 incnum(branchnum.string, num); 1020 bufautoend(&branchnum); 1021 bufscat(num, ".1"); 1022 newbranch.nextbranch = 0; 1023 } else { 1024 /* place the branch properly */ 1025 field = numlength - ((numlength&1) ^ 1); 1026 /* field of branch number */ 1027 btrail = &branchpoint->branches; 1028 while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) { 1029 btrail = &(*btrail)->nextbranch; 1030 if (!*btrail) { 1031 result = -1; 1032 break; 1033 } 1034 } 1035 if (result < 0) { 1036 /* insert/append new branchhead */ 1037 newbranch.nextbranch = *btrail; 1038 *btrail = &newbranch; 1039 if (numlength&1) bufscat(num, ".1"); 1040 } else { 1041 /* branch exists; append to end */ 1042 bufautobegin(&branchnum); 1043 getbranchno(num->string, &branchnum); 1044 targetdelta = genrevs( 1045 branchnum.string, (char*)0, (char*)0, (char*)0, 1046 &gendeltas 1047 ); 1048 bufautoend(&branchnum); 1049 if (!targetdelta) 1050 return -1; 1051 if (cmpnum(num->string,targetdelta->num) <= 0) { 1052 rcserror("revision %s too low; must be higher than %s", 1053 num->string, targetdelta->num 1054 ); 1055 return -1; 1056 } 1057 if (!removedlock 1058 && 0 <= (removedlock = removelock(targetdelta)) 1059 ) { 1060 if (numlength&1) 1061 incnum(targetdelta->num,num); 1062 targetdelta->next = &newdelta; 1063 newdelta.next = 0; 1064 } 1065 return removedlock; 1066 /* Don't do anything to newbranch. */ 1067 } 1068 } 1069 newbranch.hsh = &newdelta; 1070 newdelta.next = 0; 1071 if (branchpoint->lockedby) 1072 if (strcmp(branchpoint->lockedby, getcaller()) == 0) 1073 return removelock(branchpoint); /* This returns 1. */ 1074 return removedlock; 1075 } 1076 1077 static int 1078 addsyms(num) 1079 char const *num; 1080 { 1081 register struct Symrev *p; 1082 1083 for (p = assoclst; p; p = p->nextsym) 1084 if (addsymbol(num, p->ssymbol, p->override) < 0) 1085 return false; 1086 return true; 1087 } 1088 1089 1090 static void 1091 incnum(onum,nnum) 1092 char const *onum; 1093 struct buf *nnum; 1094 /* Increment the last field of revision number onum by one and 1095 * place the result into nnum. 1096 */ 1097 { 1098 register char *tp, *np; 1099 register size_t l; 1100 1101 l = strlen(onum); 1102 bufalloc(nnum, l+2); 1103 np = tp = nnum->string; 1104 VOID strcpy(np, onum); 1105 for (tp = np + l; np != tp; ) 1106 if (isdigit(*--tp)) { 1107 if (*tp != '9') { 1108 ++*tp; 1109 return; 1110 } 1111 *tp = '0'; 1112 } else { 1113 tp++; 1114 break; 1115 } 1116 /* We changed 999 to 000; now change it to 1000. */ 1117 *tp = '1'; 1118 tp = np + l; 1119 *tp++ = '0'; 1120 *tp = 0; 1121 } 1122 1123 1124 1125 static int 1126 removelock(delta) 1127 struct hshentry * delta; 1128 /* function: Finds the lock held by caller on delta, 1129 * removes it, and returns nonzero if successful. 1130 * Print an error message and return -1 if there is no such lock. 1131 * An exception is if !StrictLocks, and caller is the owner of 1132 * the RCS file. If caller does not have a lock in this case, 1133 * return 0; return 1 if a lock is actually removed. 1134 */ 1135 { 1136 register struct rcslock *next, **trail; 1137 char const *num; 1138 1139 num=delta->num; 1140 for (trail = &Locks; (next = *trail); trail = &next->nextlock) 1141 if (next->delta == delta) 1142 if (strcmp(getcaller(), next->login) == 0) { 1143 /* We found a lock on delta by caller; delete it. */ 1144 *trail = next->nextlock; 1145 delta->lockedby = 0; 1146 return 1; 1147 } else { 1148 rcserror("revision %s locked by %s", num, next->login); 1149 return -1; 1150 } 1151 if (!StrictLocks && myself(RCSstat.st_uid)) 1152 return 0; 1153 rcserror("no lock set by %s for revision %s", getcaller(), num); 1154 return -1; 1155 } 1156 1157 1158 1159 static char const * 1160 getcurdate() 1161 /* Return a pointer to the current date. */ 1162 { 1163 static char buffer[datesize]; /* date buffer */ 1164 1165 if (!buffer[0]) 1166 time2date(now(), buffer); 1167 return buffer; 1168 } 1169 1170 static int 1171 #if has_prototypes 1172 fixwork(mode_t newworkmode, time_t mtime) 1173 /* The `#if has_prototypes' is needed because mode_t might promote to int. */ 1174 #else 1175 fixwork(newworkmode, mtime) 1176 mode_t newworkmode; 1177 time_t mtime; 1178 #endif 1179 { 1180 return 1181 1 < workstat.st_nlink 1182 || (newworkmode&S_IWUSR && !myself(workstat.st_uid)) 1183 || setmtime(workname, mtime) != 0 1184 ? -1 1185 : workstat.st_mode == newworkmode ? 0 1186 #if has_fchmod 1187 : fchmod(Ifileno(workptr), newworkmode) == 0 ? 0 1188 #endif 1189 #if bad_chmod_close 1190 : -1 1191 #else 1192 : chmod(workname, newworkmode) 1193 #endif 1194 ; 1195 } 1196 1197 static int 1198 xpandfile(unexfile, delta, exname, dolog) 1199 RILE *unexfile; 1200 struct hshentry const *delta; 1201 char const **exname; 1202 int dolog; 1203 /* 1204 * Read unexfile and copy it to a 1205 * file, performing keyword substitution with data from delta. 1206 * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise. 1207 * If successful, stores the stream descriptor into *EXFILEP 1208 * and its name into *EXNAME. 1209 */ 1210 { 1211 char const *targetname; 1212 int e, r; 1213 1214 targetname = makedirtemp(1); 1215 if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) { 1216 eerror(targetname); 1217 workerror("can't build working file"); 1218 return -1; 1219 } 1220 r = 0; 1221 if (MIN_UNEXPAND <= Expand) 1222 fastcopy(unexfile,exfile); 1223 else { 1224 for (;;) { 1225 e = expandline( 1226 unexfile, exfile, delta, false, (FILE*)0, dolog 1227 ); 1228 if (e < 0) 1229 break; 1230 r |= e; 1231 if (e <= 1) 1232 break; 1233 } 1234 } 1235 *exname = targetname; 1236 return r & 1; 1237 } 1238 1239 1240 1241 1242 /* --------------------- G E T L O G M S G --------------------------------*/ 1243 1244 1245 static struct cbuf 1246 getlogmsg() 1247 /* Obtain and yield a log message. 1248 * If a log message is given with -m, yield that message. 1249 * If this is the initial revision, yield a standard log message. 1250 * Otherwise, reads a character string from the terminal. 1251 * Stops after reading EOF or a single '.' on a 1252 * line. getlogmsg prompts the first time it is called for the 1253 * log message; during all later calls it asks whether the previous 1254 * log message can be reused. 1255 */ 1256 { 1257 static char const 1258 emptych[] = EMPTYLOG, 1259 initialch[] = "Initial revision"; 1260 static struct cbuf const 1261 emptylog = { emptych, sizeof(emptych)-sizeof(char) }, 1262 initiallog = { initialch, sizeof(initialch)-sizeof(char) }; 1263 static struct buf logbuf; 1264 static struct cbuf logmsg; 1265 1266 register char *tp; 1267 register size_t i; 1268 char const *caller; 1269 1270 if (msg.size) return msg; 1271 1272 if (keepflag) { 1273 /* generate std. log message */ 1274 caller = getcaller(); 1275 i = sizeof(ciklog)+strlen(caller)+3; 1276 bufalloc(&logbuf, i + datesize + zonelenmax); 1277 tp = logbuf.string; 1278 VOID sprintf(tp, "%s%s at ", ciklog, caller); 1279 VOID date2str(getcurdate(), tp+i); 1280 logmsg.string = tp; 1281 logmsg.size = strlen(tp); 1282 return logmsg; 1283 } 1284 1285 if (!targetdelta && ( 1286 cmpnum(newdelnum.string,"1.1")==0 || 1287 cmpnum(newdelnum.string,"1.0")==0 1288 )) 1289 return initiallog; 1290 1291 if (logmsg.size) { 1292 /*previous log available*/ 1293 if (yesorno(true, "reuse log message of previous file? [yn](y): ")) 1294 return logmsg; 1295 } 1296 1297 /* now read string from stdin */ 1298 logmsg = getsstdin("m", "log message", "", &logbuf); 1299 1300 /* now check whether the log message is not empty */ 1301 if (logmsg.size) 1302 return logmsg; 1303 return emptylog; 1304 } 1305 1306 /* Make a linked list of Symbolic names */ 1307 1308 static void 1309 addassoclst(flag, sp) 1310 int flag; 1311 char const *sp; 1312 { 1313 struct Symrev *pt; 1314 1315 pt = talloc(struct Symrev); 1316 pt->ssymbol = sp; 1317 pt->override = flag; 1318 pt->nextsym = 0; 1319 *nextassoc = pt; 1320 nextassoc = &pt->nextsym; 1321 } 1322