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.3 2007/01/17 17:56:23 y0netan1 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 enum {RANDOM_BYTES = 8}; 268 enum {COMMITID_RAW_SIZE = (sizeof(time_t) + RANDOM_BYTES)}; 269 static void convert P((char const input[COMMITID_RAW_SIZE], char *output)); 270 271 static FILE *exfile; 272 static RILE *workptr; /* working file pointer */ 273 static struct buf newdelnum; /* new revision number */ 274 static struct cbuf msg; 275 static int exitstatus; 276 static int forceciflag; /* forces check in */ 277 static int keepflag, keepworkingfile, rcsinitflag; 278 static struct hshentries *gendeltas; /* deltas to be generated */ 279 static struct hshentry *targetdelta; /* old delta to be generated */ 280 static struct hshentry newdelta; /* new delta to be inserted */ 281 static struct stat workstat; 282 static struct Symrev *assoclst, **nextassoc; 283 284 mainProg(ciId, "ci", "$DragonFly: src/gnu/usr.bin/rcs/ci/ci.c,v 1.3 2007/01/17 17:56:23 y0netan1 Exp $") 285 { 286 static char const cmdusage[] = 287 "\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -T -Vn -wwho -xsuff -zzone file ..."; 288 static char const default_state[] = DEFAULTSTATE; 289 290 char altdate[datesize]; 291 char olddate[datesize]; 292 char newdatebuf[datesize + zonelenmax]; 293 char targetdatebuf[datesize + zonelenmax]; 294 char commitid[commitidsize]; 295 char *a, **newargv, *textfile; 296 char const *author, *krev, *rev, *state; 297 char const *diffname, *expname; 298 char const *newworkname; 299 int initflag, mustread; 300 int lockflag, lockthis, mtimeflag, removedlock, Ttimeflag; 301 int r; 302 int changedRCS, changework, dolog, newhead; 303 int usestatdate; /* Use mod time of file for -d. */ 304 mode_t newworkmode; /* mode for working file */ 305 time_t mtime, wtime; 306 struct hshentry *workdelta; 307 308 setrid(); 309 310 author = rev = state = textfile = 0; 311 initflag = lockflag = mustread = false; 312 mtimeflag = false; 313 Ttimeflag = false; 314 altdate[0]= '\0'; /* empty alternate date for -d */ 315 usestatdate=false; 316 suffixes = X_DEFAULT; 317 nextassoc = &assoclst; 318 319 { 320 char buf[COMMITID_RAW_SIZE] = { 0, }; 321 ssize_t len = 0; 322 time_t rightnow = time (NULL); 323 char *startrand = buf + sizeof (time_t); 324 unsigned char *p = (unsigned char *) startrand; 325 size_t randbytes = RANDOM_BYTES; 326 int flags = O_RDONLY; 327 int fd; 328 #ifdef O_NOCTTY 329 flags |= O_NOCTTY; 330 #endif 331 if (rightnow != (time_t)-1) 332 while (rightnow > 0) { 333 *--p = rightnow % (UCHAR_MAX + 1); 334 rightnow /= UCHAR_MAX + 1; 335 } 336 else { 337 /* try to use more random data */ 338 randbytes = COMMITID_RAW_SIZE; 339 startrand = buf; 340 } 341 fd = open (urandom_dev, flags); 342 if (fd >= 0) { 343 len = read (fd, startrand, randbytes); 344 close (fd); 345 } 346 if (len <= 0) { 347 /* no random data was available so use pid */ 348 long int pid = (long int)getpid (); 349 p = (unsigned char *) (startrand + sizeof (pid)); 350 while (pid > 0) { 351 *--p = pid % (UCHAR_MAX + 1); 352 pid /= UCHAR_MAX + 1; 353 } 354 } 355 convert(buf, commitid); 356 } 357 358 argc = getRCSINIT(argc, argv, &newargv); 359 argv = newargv; 360 while (a = *++argv, 0<--argc && *a++=='-') { 361 switch (*a++) { 362 363 case 'r': 364 if (*a) 365 goto revno; 366 keepworkingfile = lockflag = false; 367 break; 368 369 case 'l': 370 keepworkingfile = lockflag = true; 371 revno: 372 if (*a) { 373 if (rev) warn("redefinition of revision number"); 374 rev = a; 375 } 376 break; 377 378 case 'u': 379 keepworkingfile=true; lockflag=false; 380 goto revno; 381 382 case 'i': 383 initflag = true; 384 goto revno; 385 386 case 'j': 387 mustread = true; 388 goto revno; 389 390 case 'I': 391 interactiveflag = true; 392 goto revno; 393 394 case 'q': 395 quietflag=true; 396 goto revno; 397 398 case 'f': 399 forceciflag=true; 400 goto revno; 401 402 case 'k': 403 keepflag=true; 404 goto revno; 405 406 case 'm': 407 if (msg.size) redefined('m'); 408 msg = cleanlogmsg(a, strlen(a)); 409 if (!msg.size) 410 error("missing message for -m option"); 411 break; 412 413 case 'n': 414 if (!*a) { 415 error("missing symbolic name after -n"); 416 break; 417 } 418 checkssym(a); 419 addassoclst(false, a); 420 break; 421 422 case 'N': 423 if (!*a) { 424 error("missing symbolic name after -N"); 425 break; 426 } 427 checkssym(a); 428 addassoclst(true, a); 429 break; 430 431 case 's': 432 if (*a) { 433 if (state) redefined('s'); 434 checksid(a); 435 state = a; 436 } else 437 error("missing state for -s option"); 438 break; 439 440 case 't': 441 if (*a) { 442 if (textfile) redefined('t'); 443 textfile = a; 444 } 445 break; 446 447 case 'd': 448 if (altdate[0] || usestatdate) 449 redefined('d'); 450 altdate[0] = '\0'; 451 if (!(usestatdate = !*a)) 452 str2date(a, altdate); 453 break; 454 455 case 'M': 456 mtimeflag = true; 457 goto revno; 458 459 case 'w': 460 if (*a) { 461 if (author) redefined('w'); 462 checksid(a); 463 author = a; 464 } else 465 error("missing author for -w option"); 466 break; 467 468 case 'x': 469 suffixes = a; 470 break; 471 472 case 'V': 473 setRCSversion(*argv); 474 break; 475 476 case 'z': 477 zone_set(a); 478 break; 479 480 case 'T': 481 if (!*a) { 482 Ttimeflag = true; 483 break; 484 } 485 /* fall into */ 486 default: 487 error("unknown option: %s%s", *argv, cmdusage); 488 }; 489 } /* end processing of options */ 490 491 /* Handle all pathnames. */ 492 if (nerror) cleanup(); 493 else if (argc < 1) faterror("no input file%s", cmdusage); 494 else for (; 0 < argc; cleanup(), ++argv, --argc) { 495 targetdelta = 0; 496 ffree(); 497 498 switch (pairnames(argc, argv, rcswriteopen, mustread, false)) { 499 500 case -1: /* New RCS file */ 501 # if has_setuid && has_getuid 502 if (euid() != ruid()) { 503 workerror("setuid initial checkin prohibited; use `rcs -i -a' first"); 504 continue; 505 } 506 # endif 507 rcsinitflag = true; 508 break; 509 510 case 0: /* Error */ 511 continue; 512 513 case 1: /* Normal checkin with prev . RCS file */ 514 if (initflag) { 515 rcserror("already exists"); 516 continue; 517 } 518 rcsinitflag = !Head; 519 } 520 521 /* 522 * RCSname contains the name of the RCS file, and 523 * workname contains the name of the working file. 524 * If the RCS file exists, finptr contains the file descriptor for the 525 * RCS file, and RCSstat is set. The admin node is initialized. 526 */ 527 528 diagnose("%s <-- %s\n", RCSname, workname); 529 530 if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) { 531 eerror(workname); 532 continue; 533 } 534 535 if (finptr) { 536 if (same_file(RCSstat, workstat, 0)) { 537 rcserror("RCS file is the same as working file %s.", 538 workname 539 ); 540 continue; 541 } 542 if (!checkaccesslist()) 543 continue; 544 } 545 546 krev = rev; 547 if (keepflag) { 548 /* get keyword values from working file */ 549 if (!getoldkeys(workptr)) continue; 550 if (!rev && !*(krev = prevrev.string)) { 551 workerror("can't find a revision number"); 552 continue; 553 } 554 if (!*prevdate.string && *altdate=='\0' && usestatdate==false) 555 workwarn("can't find a date"); 556 if (!*prevauthor.string && !author) 557 workwarn("can't find an author"); 558 if (!*prevstate.string && !state) 559 workwarn("can't find a state"); 560 } /* end processing keepflag */ 561 562 /* Read the delta tree. */ 563 if (finptr) 564 gettree(); 565 566 /* expand symbolic revision number */ 567 if (!fexpandsym(krev, &newdelnum, workptr)) 568 continue; 569 570 /* splice new delta into tree */ 571 if ((removedlock = addelta()) < 0) 572 continue; 573 574 newdelta.num = newdelnum.string; 575 newdelta.branches = 0; 576 newdelta.lockedby = 0; /* This might be changed by addlock(). */ 577 newdelta.selector = true; 578 newdelta.name = 0; 579 clear_buf(&newdelta.ig); 580 clear_buf(&newdelta.igtext); 581 /* set commitid */ 582 newdelta.commitid = commitid; 583 /* set author */ 584 if (author) 585 newdelta.author=author; /* set author given by -w */ 586 else if (keepflag && *prevauthor.string) 587 newdelta.author=prevauthor.string; /* preserve old author if possible*/ 588 else newdelta.author=getcaller();/* otherwise use caller's id */ 589 newdelta.state = default_state; 590 if (state) 591 newdelta.state=state; /* set state given by -s */ 592 else if (keepflag && *prevstate.string) 593 newdelta.state=prevstate.string; /* preserve old state if possible */ 594 if (usestatdate) { 595 time2date(workstat.st_mtime, altdate); 596 } 597 if (*altdate!='\0') 598 newdelta.date=altdate; /* set date given by -d */ 599 else if (keepflag && *prevdate.string) { 600 /* Preserve old date if possible. */ 601 str2date(prevdate.string, olddate); 602 newdelta.date = olddate; 603 } else 604 newdelta.date = getcurdate(); /* use current date */ 605 /* now check validity of date -- needed because of -d and -k */ 606 if (targetdelta && 607 cmpdate(newdelta.date,targetdelta->date) < 0) { 608 rcserror("Date %s precedes %s in revision %s.", 609 date2str(newdelta.date, newdatebuf), 610 date2str(targetdelta->date, targetdatebuf), 611 targetdelta->num 612 ); 613 continue; 614 } 615 616 617 if (lockflag && addlock(&newdelta, true) < 0) continue; 618 619 if (keepflag && *prevname.string) 620 if (addsymbol(newdelta.num, prevname.string, false) < 0) 621 continue; 622 if (!addsyms(newdelta.num)) 623 continue; 624 625 626 putadmin(); 627 puttree(Head,frewrite); 628 putdesc(false,textfile); 629 630 changework = Expand < MIN_UNCHANGED_EXPAND; 631 dolog = true; 632 lockthis = lockflag; 633 workdelta = &newdelta; 634 635 /* build rest of file */ 636 if (rcsinitflag) { 637 diagnose("initial revision: %s\n", newdelta.num); 638 /* get logmessage */ 639 newdelta.log=getlogmsg(); 640 putdftext(&newdelta, workptr, frewrite, false); 641 RCSstat.st_mode = workstat.st_mode; 642 RCSstat.st_nlink = 0; 643 changedRCS = true; 644 } else { 645 diffname = maketemp(0); 646 newhead = Head == &newdelta; 647 if (!newhead) 648 foutptr = frewrite; 649 expname = buildrevision( 650 gendeltas, targetdelta, (FILE*)0, false 651 ); 652 if ( 653 !forceciflag && 654 strcmp(newdelta.state, targetdelta->state) == 0 && 655 (changework = rcsfcmp( 656 workptr, &workstat, expname, targetdelta 657 )) <= 0 658 ) { 659 diagnose("file is unchanged; reverting to previous revision %s\n", 660 targetdelta->num 661 ); 662 if (removedlock < lockflag) { 663 diagnose("previous revision was not locked; ignoring -l option\n"); 664 lockthis = 0; 665 } 666 dolog = false; 667 if (! (changedRCS = lockflag<removedlock || assoclst)) 668 workdelta = targetdelta; 669 else { 670 /* 671 * We have started to build the wrong new RCS file. 672 * Start over from the beginning. 673 */ 674 long hwm = ftell(frewrite); 675 int bad_truncate; 676 Orewind(frewrite); 677 678 /* 679 * Work around a common ftruncate() bug: 680 * NFS won't let you truncate a file that you 681 * currently lack permissions for, even if you 682 * had permissions when you opened it. 683 * Also, Posix 1003.1b-1993 sec 5.6.7.2 p 128 l 1022 684 * says ftruncate might fail because it's not supported. 685 */ 686 # if !has_ftruncate 687 # undef ftruncate 688 # define ftruncate(fd,length) (-1) 689 # endif 690 bad_truncate = ftruncate(fileno(frewrite), (off_t)0); 691 692 Irewind(finptr); 693 Lexinit(); 694 getadmin(); 695 gettree(); 696 if (!(workdelta = genrevs( 697 targetdelta->num, (char*)0, (char*)0, (char*)0, 698 &gendeltas 699 ))) 700 continue; 701 workdelta->log = targetdelta->log; 702 if (newdelta.state != default_state) 703 workdelta->state = newdelta.state; 704 if (lockthis<removedlock && removelock(workdelta)<0) 705 continue; 706 if (!addsyms(workdelta->num)) 707 continue; 708 if (dorewrite(true, true) != 0) 709 continue; 710 fastcopy(finptr, frewrite); 711 if (bad_truncate) 712 while (ftell(frewrite) < hwm) 713 /* White out any earlier mistake with '\n's. */ 714 /* This is unlikely. */ 715 afputc('\n', frewrite); 716 } 717 } else { 718 int wfd = Ifileno(workptr); 719 struct stat checkworkstat; 720 char const *diffv[6 + !!OPEN_O_BINARY], **diffp; 721 # if large_memory && !maps_memory 722 FILE *wfile = workptr->stream; 723 long wfile_off; 724 # endif 725 # if !has_fflush_input && !(large_memory && maps_memory) 726 off_t wfd_off; 727 # endif 728 729 diagnose("new revision: %s; previous revision: %s\n", 730 newdelta.num, targetdelta->num 731 ); 732 newdelta.log = getlogmsg(); 733 # if !large_memory 734 Irewind(workptr); 735 # if has_fflush_input 736 if (fflush(workptr) != 0) 737 Ierror(); 738 # endif 739 # else 740 # if !maps_memory 741 if ( 742 (wfile_off = ftell(wfile)) == -1 743 || fseek(wfile, 0L, SEEK_SET) != 0 744 # if has_fflush_input 745 || fflush(wfile) != 0 746 # endif 747 ) 748 Ierror(); 749 # endif 750 # endif 751 # if !has_fflush_input && !(large_memory && maps_memory) 752 wfd_off = lseek(wfd, (off_t)0, SEEK_CUR); 753 if (wfd_off == -1 754 || (wfd_off != 0 755 && lseek(wfd, (off_t)0, SEEK_SET) != 0)) 756 Ierror(); 757 # endif 758 diffp = diffv; 759 *++diffp = DIFF; 760 *++diffp = DIFFFLAGS; 761 # if OPEN_O_BINARY 762 if (Expand == BINARY_EXPAND) 763 *++diffp = "--binary"; 764 # endif 765 *++diffp = newhead ? "-" : expname; 766 *++diffp = newhead ? expname : "-"; 767 *++diffp = 0; 768 switch (runv(wfd, diffname, diffv)) { 769 case DIFF_FAILURE: case DIFF_SUCCESS: break; 770 default: rcsfaterror("diff failed"); 771 } 772 # if !has_fflush_input && !(large_memory && maps_memory) 773 if (lseek(wfd, wfd_off, SEEK_CUR) == -1) 774 Ierror(); 775 # endif 776 # if large_memory && !maps_memory 777 if (fseek(wfile, wfile_off, SEEK_SET) != 0) 778 Ierror(); 779 # endif 780 if (newhead) { 781 Irewind(workptr); 782 putdftext(&newdelta, workptr, frewrite, false); 783 if (!putdtext(targetdelta,diffname,frewrite,true)) continue; 784 } else 785 if (!putdtext(&newdelta,diffname,frewrite,true)) continue; 786 787 /* 788 * Check whether the working file changed during checkin, 789 * to avoid producing an inconsistent RCS file. 790 */ 791 if ( 792 fstat(wfd, &checkworkstat) != 0 793 || workstat.st_mtime != checkworkstat.st_mtime 794 || workstat.st_size != checkworkstat.st_size 795 ) { 796 workerror("file changed during checkin"); 797 continue; 798 } 799 800 changedRCS = true; 801 } 802 } 803 804 /* Deduce time_t of new revision if it is needed later. */ 805 wtime = (time_t)-1; 806 if (mtimeflag | Ttimeflag) 807 wtime = date2time(workdelta->date); 808 809 if (donerewrite(changedRCS, 810 !Ttimeflag ? (time_t)-1 811 : finptr && wtime < RCSstat.st_mtime ? RCSstat.st_mtime 812 : wtime 813 ) != 0) 814 continue; 815 816 if (!keepworkingfile) { 817 Izclose(&workptr); 818 r = un_link(workname); /* Get rid of old file */ 819 } else { 820 newworkmode = WORKMODE(RCSstat.st_mode, 821 ! (Expand==VAL_EXPAND || lockthis < StrictLocks) 822 ); 823 mtime = mtimeflag ? wtime : (time_t)-1; 824 825 /* Expand if it might change or if we can't fix mode, time. */ 826 if (changework || (r=fixwork(newworkmode,mtime)) != 0) { 827 Irewind(workptr); 828 /* Expand keywords in file. */ 829 locker_expansion = lockthis; 830 workdelta->name = 831 namedrev( 832 assoclst ? assoclst->ssymbol 833 : keepflag && *prevname.string ? prevname.string 834 : rev, 835 workdelta 836 ); 837 switch (xpandfile( 838 workptr, workdelta, &newworkname, dolog 839 )) { 840 default: 841 continue; 842 843 case 0: 844 /* 845 * No expansion occurred; try to reuse working file 846 * unless we already tried and failed. 847 */ 848 if (changework) 849 if ((r=fixwork(newworkmode,mtime)) == 0) 850 break; 851 /* fall into */ 852 case 1: 853 Izclose(&workptr); 854 aflush(exfile); 855 ignoreints(); 856 r = chnamemod(&exfile, newworkname, 857 workname, 1, newworkmode, mtime 858 ); 859 keepdirtemp(newworkname); 860 restoreints(); 861 } 862 } 863 } 864 if (r != 0) { 865 eerror(workname); 866 continue; 867 } 868 diagnose("done\n"); 869 870 } 871 872 tempunlink(); 873 exitmain(exitstatus); 874 } /* end of main (ci) */ 875 876 static void 877 cleanup() 878 { 879 if (nerror) exitstatus = EXIT_FAILURE; 880 Izclose(&finptr); 881 Izclose(&workptr); 882 Ozclose(&exfile); 883 Ozclose(&fcopy); 884 ORCSclose(); 885 dirtempunlink(); 886 } 887 888 #if RCS_lint 889 # define exiterr ciExit 890 #endif 891 void 892 exiterr() 893 { 894 ORCSerror(); 895 dirtempunlink(); 896 tempunlink(); 897 _exit(EXIT_FAILURE); 898 } 899 900 /*****************************************************************/ 901 /* the rest are auxiliary routines */ 902 903 904 static int 905 addelta() 906 /* Function: Appends a delta to the delta tree, whose number is 907 * given by newdelnum. Updates Head, newdelnum, newdelnumlength, 908 * and the links in newdelta. 909 * Return -1 on error, 1 if a lock is removed, 0 otherwise. 910 */ 911 { 912 register char *tp; 913 register int i; 914 int removedlock; 915 int newdnumlength; /* actual length of new rev. num. */ 916 917 newdnumlength = countnumflds(newdelnum.string); 918 919 if (rcsinitflag) { 920 /* this covers non-existing RCS file and a file initialized with rcs -i */ 921 if (newdnumlength==0 && Dbranch) { 922 bufscpy(&newdelnum, Dbranch); 923 newdnumlength = countnumflds(Dbranch); 924 } 925 if (newdnumlength==0) bufscpy(&newdelnum, "1.1"); 926 else if (newdnumlength==1) bufscat(&newdelnum, ".1"); 927 else if (newdnumlength>2) { 928 rcserror("Branch point doesn't exist for revision %s.", 929 newdelnum.string 930 ); 931 return -1; 932 } /* newdnumlength == 2 is OK; */ 933 Head = &newdelta; 934 newdelta.next = 0; 935 return 0; 936 } 937 if (newdnumlength==0) { 938 /* derive new revision number from locks */ 939 switch (findlock(true, &targetdelta)) { 940 941 default: 942 /* found two or more old locks */ 943 return -1; 944 945 case 1: 946 /* found an old lock */ 947 /* check whether locked revision exists */ 948 if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas)) 949 return -1; 950 if (targetdelta==Head) { 951 /* make new head */ 952 newdelta.next=Head; 953 Head= &newdelta; 954 } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) { 955 /* new tip revision on side branch */ 956 targetdelta->next= &newdelta; 957 newdelta.next = 0; 958 } else { 959 /* middle revision; start a new branch */ 960 bufscpy(&newdelnum, ""); 961 return addbranch(targetdelta, &newdelnum, 1); 962 } 963 incnum(targetdelta->num, &newdelnum); 964 return 1; /* successful use of existing lock */ 965 966 case 0: 967 /* no existing lock; try Dbranch */ 968 /* update newdelnum */ 969 if (StrictLocks || !myself(RCSstat.st_uid)) { 970 rcserror("no lock set by %s", getcaller()); 971 return -1; 972 } 973 if (Dbranch) { 974 bufscpy(&newdelnum, Dbranch); 975 } else { 976 incnum(Head->num, &newdelnum); 977 } 978 newdnumlength = countnumflds(newdelnum.string); 979 /* now fall into next statement */ 980 } 981 } 982 if (newdnumlength<=2) { 983 /* add new head per given number */ 984 if(newdnumlength==1) { 985 /* make a two-field number out of it*/ 986 if (cmpnumfld(newdelnum.string,Head->num,1)==0) 987 incnum(Head->num, &newdelnum); 988 else 989 bufscat(&newdelnum, ".1"); 990 } 991 if (cmpnum(newdelnum.string,Head->num) <= 0) { 992 rcserror("revision %s too low; must be higher than %s", 993 newdelnum.string, Head->num 994 ); 995 return -1; 996 } 997 targetdelta = Head; 998 if (0 <= (removedlock = removelock(Head))) { 999 if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas)) 1000 return -1; 1001 newdelta.next = Head; 1002 Head = &newdelta; 1003 } 1004 return removedlock; 1005 } else { 1006 /* put new revision on side branch */ 1007 /*first, get branch point */ 1008 tp = newdelnum.string; 1009 for (i = newdnumlength - ((newdnumlength&1) ^ 1); --i; ) 1010 while (*tp++ != '.') 1011 continue; 1012 *--tp = 0; /* Kill final dot to get old delta temporarily. */ 1013 if (!(targetdelta=genrevs(newdelnum.string,(char*)0,(char*)0,(char*)0,&gendeltas))) 1014 return -1; 1015 if (cmpnum(targetdelta->num, newdelnum.string) != 0) { 1016 rcserror("can't find branch point %s", newdelnum.string); 1017 return -1; 1018 } 1019 *tp = '.'; /* Restore final dot. */ 1020 return addbranch(targetdelta, &newdelnum, 0); 1021 } 1022 } 1023 1024 1025 1026 static int 1027 addbranch(branchpoint, num, removedlock) 1028 struct hshentry *branchpoint; 1029 struct buf *num; 1030 int removedlock; 1031 /* adds a new branch and branch delta at branchpoint. 1032 * If num is the null string, appends the new branch, incrementing 1033 * the highest branch number (initially 1), and setting the level number to 1. 1034 * the new delta and branchhead are in globals newdelta and newbranch, resp. 1035 * the new number is placed into num. 1036 * Return -1 on error, 1 if a lock is removed, 0 otherwise. 1037 * If REMOVEDLOCK is 1, a lock was already removed. 1038 */ 1039 { 1040 struct branchhead *bhead, **btrail; 1041 struct buf branchnum; 1042 int result; 1043 int field, numlength; 1044 static struct branchhead newbranch; /* new branch to be inserted */ 1045 1046 numlength = countnumflds(num->string); 1047 1048 if (!branchpoint->branches) { 1049 /* start first branch */ 1050 branchpoint->branches = &newbranch; 1051 if (numlength==0) { 1052 bufscpy(num, branchpoint->num); 1053 bufscat(num, ".1.1"); 1054 } else if (numlength&1) 1055 bufscat(num, ".1"); 1056 newbranch.nextbranch = 0; 1057 1058 } else if (numlength==0) { 1059 /* append new branch to the end */ 1060 bhead=branchpoint->branches; 1061 while (bhead->nextbranch) bhead=bhead->nextbranch; 1062 bhead->nextbranch = &newbranch; 1063 bufautobegin(&branchnum); 1064 getbranchno(bhead->hsh->num, &branchnum); 1065 incnum(branchnum.string, num); 1066 bufautoend(&branchnum); 1067 bufscat(num, ".1"); 1068 newbranch.nextbranch = 0; 1069 } else { 1070 /* place the branch properly */ 1071 field = numlength - ((numlength&1) ^ 1); 1072 /* field of branch number */ 1073 btrail = &branchpoint->branches; 1074 while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) { 1075 btrail = &(*btrail)->nextbranch; 1076 if (!*btrail) { 1077 result = -1; 1078 break; 1079 } 1080 } 1081 if (result < 0) { 1082 /* insert/append new branchhead */ 1083 newbranch.nextbranch = *btrail; 1084 *btrail = &newbranch; 1085 if (numlength&1) bufscat(num, ".1"); 1086 } else { 1087 /* branch exists; append to end */ 1088 bufautobegin(&branchnum); 1089 getbranchno(num->string, &branchnum); 1090 targetdelta = genrevs( 1091 branchnum.string, (char*)0, (char*)0, (char*)0, 1092 &gendeltas 1093 ); 1094 bufautoend(&branchnum); 1095 if (!targetdelta) 1096 return -1; 1097 if (cmpnum(num->string,targetdelta->num) <= 0) { 1098 rcserror("revision %s too low; must be higher than %s", 1099 num->string, targetdelta->num 1100 ); 1101 return -1; 1102 } 1103 if (!removedlock 1104 && 0 <= (removedlock = removelock(targetdelta)) 1105 ) { 1106 if (numlength&1) 1107 incnum(targetdelta->num,num); 1108 targetdelta->next = &newdelta; 1109 newdelta.next = 0; 1110 } 1111 return removedlock; 1112 /* Don't do anything to newbranch. */ 1113 } 1114 } 1115 newbranch.hsh = &newdelta; 1116 newdelta.next = 0; 1117 if (branchpoint->lockedby) 1118 if (strcmp(branchpoint->lockedby, getcaller()) == 0) 1119 return removelock(branchpoint); /* This returns 1. */ 1120 return removedlock; 1121 } 1122 1123 static int 1124 addsyms(num) 1125 char const *num; 1126 { 1127 register struct Symrev *p; 1128 1129 for (p = assoclst; p; p = p->nextsym) 1130 if (addsymbol(num, p->ssymbol, p->override) < 0) 1131 return false; 1132 return true; 1133 } 1134 1135 1136 static void 1137 incnum(onum,nnum) 1138 char const *onum; 1139 struct buf *nnum; 1140 /* Increment the last field of revision number onum by one and 1141 * place the result into nnum. 1142 */ 1143 { 1144 register char *tp, *np; 1145 register size_t l; 1146 1147 l = strlen(onum); 1148 bufalloc(nnum, l+2); 1149 np = tp = nnum->string; 1150 VOID strcpy(np, onum); 1151 for (tp = np + l; np != tp; ) 1152 if (isdigit(*--tp)) { 1153 if (*tp != '9') { 1154 ++*tp; 1155 return; 1156 } 1157 *tp = '0'; 1158 } else { 1159 tp++; 1160 break; 1161 } 1162 /* We changed 999 to 000; now change it to 1000. */ 1163 *tp = '1'; 1164 tp = np + l; 1165 *tp++ = '0'; 1166 *tp = 0; 1167 } 1168 1169 1170 1171 static int 1172 removelock(delta) 1173 struct hshentry * delta; 1174 /* function: Finds the lock held by caller on delta, 1175 * removes it, and returns nonzero if successful. 1176 * Print an error message and return -1 if there is no such lock. 1177 * An exception is if !StrictLocks, and caller is the owner of 1178 * the RCS file. If caller does not have a lock in this case, 1179 * return 0; return 1 if a lock is actually removed. 1180 */ 1181 { 1182 register struct rcslock *next, **trail; 1183 char const *num; 1184 1185 num=delta->num; 1186 for (trail = &Locks; (next = *trail); trail = &next->nextlock) 1187 if (next->delta == delta) 1188 if (strcmp(getcaller(), next->login) == 0) { 1189 /* We found a lock on delta by caller; delete it. */ 1190 *trail = next->nextlock; 1191 delta->lockedby = 0; 1192 return 1; 1193 } else { 1194 rcserror("revision %s locked by %s", num, next->login); 1195 return -1; 1196 } 1197 if (!StrictLocks && myself(RCSstat.st_uid)) 1198 return 0; 1199 rcserror("no lock set by %s for revision %s", getcaller(), num); 1200 return -1; 1201 } 1202 1203 1204 1205 static char const * 1206 getcurdate() 1207 /* Return a pointer to the current date. */ 1208 { 1209 static char buffer[datesize]; /* date buffer */ 1210 1211 if (!buffer[0]) 1212 time2date(now(), buffer); 1213 return buffer; 1214 } 1215 1216 static int 1217 #if has_prototypes 1218 fixwork(mode_t newworkmode, time_t mtime) 1219 /* The `#if has_prototypes' is needed because mode_t might promote to int. */ 1220 #else 1221 fixwork(newworkmode, mtime) 1222 mode_t newworkmode; 1223 time_t mtime; 1224 #endif 1225 { 1226 return 1227 1 < workstat.st_nlink 1228 || (newworkmode&S_IWUSR && !myself(workstat.st_uid)) 1229 || setmtime(workname, mtime) != 0 1230 ? -1 1231 : workstat.st_mode == newworkmode ? 0 1232 #if has_fchmod 1233 : fchmod(Ifileno(workptr), newworkmode) == 0 ? 0 1234 #endif 1235 #if bad_chmod_close 1236 : -1 1237 #else 1238 : chmod(workname, newworkmode) 1239 #endif 1240 ; 1241 } 1242 1243 static int 1244 xpandfile(unexfile, delta, exname, dolog) 1245 RILE *unexfile; 1246 struct hshentry const *delta; 1247 char const **exname; 1248 int dolog; 1249 /* 1250 * Read unexfile and copy it to a 1251 * file, performing keyword substitution with data from delta. 1252 * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise. 1253 * If successful, stores the stream descriptor into *EXFILEP 1254 * and its name into *EXNAME. 1255 */ 1256 { 1257 char const *targetname; 1258 int e, r; 1259 1260 targetname = makedirtemp(1); 1261 if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) { 1262 eerror(targetname); 1263 workerror("can't build working file"); 1264 return -1; 1265 } 1266 r = 0; 1267 if (MIN_UNEXPAND <= Expand) 1268 fastcopy(unexfile,exfile); 1269 else { 1270 for (;;) { 1271 e = expandline( 1272 unexfile, exfile, delta, false, (FILE*)0, dolog 1273 ); 1274 if (e < 0) 1275 break; 1276 r |= e; 1277 if (e <= 1) 1278 break; 1279 } 1280 } 1281 *exname = targetname; 1282 return r & 1; 1283 } 1284 1285 1286 1287 1288 /* --------------------- G E T L O G M S G --------------------------------*/ 1289 1290 1291 static struct cbuf 1292 getlogmsg() 1293 /* Obtain and yield a log message. 1294 * If a log message is given with -m, yield that message. 1295 * If this is the initial revision, yield a standard log message. 1296 * Otherwise, reads a character string from the terminal. 1297 * Stops after reading EOF or a single '.' on a 1298 * line. getlogmsg prompts the first time it is called for the 1299 * log message; during all later calls it asks whether the previous 1300 * log message can be reused. 1301 */ 1302 { 1303 static char const 1304 emptych[] = EMPTYLOG, 1305 initialch[] = "Initial revision"; 1306 static struct cbuf const 1307 emptylog = { emptych, sizeof(emptych)-sizeof(char) }, 1308 initiallog = { initialch, sizeof(initialch)-sizeof(char) }; 1309 static struct buf logbuf; 1310 static struct cbuf logmsg; 1311 1312 register char *tp; 1313 register size_t i; 1314 char const *caller; 1315 1316 if (msg.size) return msg; 1317 1318 if (keepflag) { 1319 /* generate std. log message */ 1320 caller = getcaller(); 1321 i = sizeof(ciklog)+strlen(caller)+3; 1322 bufalloc(&logbuf, i + datesize + zonelenmax); 1323 tp = logbuf.string; 1324 VOID sprintf(tp, "%s%s at ", ciklog, caller); 1325 VOID date2str(getcurdate(), tp+i); 1326 logmsg.string = tp; 1327 logmsg.size = strlen(tp); 1328 return logmsg; 1329 } 1330 1331 if (!targetdelta && ( 1332 cmpnum(newdelnum.string,"1.1")==0 || 1333 cmpnum(newdelnum.string,"1.0")==0 1334 )) 1335 return initiallog; 1336 1337 if (logmsg.size) { 1338 /*previous log available*/ 1339 if (yesorno(true, "reuse log message of previous file? [yn](y): ")) 1340 return logmsg; 1341 } 1342 1343 /* now read string from stdin */ 1344 logmsg = getsstdin("m", "log message", "", &logbuf); 1345 1346 /* now check whether the log message is not empty */ 1347 if (logmsg.size) 1348 return logmsg; 1349 return emptylog; 1350 } 1351 1352 /* Make a linked list of Symbolic names */ 1353 1354 static void 1355 addassoclst(flag, sp) 1356 int flag; 1357 char const *sp; 1358 { 1359 struct Symrev *pt; 1360 1361 pt = talloc(struct Symrev); 1362 pt->ssymbol = sp; 1363 pt->override = flag; 1364 pt->nextsym = 0; 1365 *nextassoc = pt; 1366 nextassoc = &pt->nextsym; 1367 } 1368 1369 static char const alphabet[62] = 1370 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 1371 1372 /* Divide BUF by D, returning the remainder. Replace BUF by the 1373 quotient. BUF[0] is the most significant part of BUF. 1374 D must not exceed UINT_MAX >> CHAR_BIT. */ 1375 static unsigned int 1376 divide_by (unsigned char buf[COMMITID_RAW_SIZE], unsigned int d) 1377 { 1378 unsigned int carry = 0; 1379 int i; 1380 for (i = 0; i < COMMITID_RAW_SIZE; i++) 1381 { 1382 unsigned int byte = buf[i]; 1383 unsigned int dividend = (carry << CHAR_BIT) + byte; 1384 buf[i] = dividend / d; 1385 carry = dividend % d; 1386 } 1387 return carry; 1388 } 1389 1390 static void 1391 convert (char const input[COMMITID_RAW_SIZE], char *output) 1392 { 1393 static char const zero[COMMITID_RAW_SIZE] = { 0, }; 1394 unsigned char buf[COMMITID_RAW_SIZE]; 1395 size_t o = 0; 1396 memcpy (buf, input, COMMITID_RAW_SIZE); 1397 while (memcmp (buf, zero, COMMITID_RAW_SIZE) != 0) 1398 output[o++] = alphabet[divide_by (buf, sizeof alphabet)]; 1399 if (! o) 1400 output[o++] = '0'; 1401 output[o] = '\0'; 1402 } 1403