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