xref: /dragonfly/gnu/usr.bin/rcs/ci/ci.c (revision 6e285212)
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