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