1/*	$NetBSD: ci.c,v 1.2 2016/01/14 04:22:39 christos Exp $	*/
2
3/* Check in revisions of RCS files from working files.  */
4
5/* Copyright 1982, 1988, 1989 Walter Tichy
6   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
7   Distributed under license by the Free Software Foundation, Inc.
8
9This file is part of RCS.
10
11RCS is free software; you can redistribute it and/or modify
12it under the terms of the GNU General Public License as published by
13the Free Software Foundation; either version 2, or (at your option)
14any later version.
15
16RCS is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU General Public License for more details.
20
21You should have received a copy of the GNU General Public License
22along with RCS; see the file COPYING.
23If not, write to the Free Software Foundation,
2459 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26Report problems and direct all questions to:
27
28    rcs-bugs@cs.purdue.edu
29
30*/
31
32/*
33 * Log: ci.c,v
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
249struct Symrev {
250       char const *ssymbol;
251       int override;
252       struct Symrev * nextsym;
253};
254
255static char const *getcurdate P((void));
256static int addbranch P((struct hshentry*,struct buf*,int));
257static int addelta P((void));
258static int addsyms P((char const*));
259static int fixwork P((mode_t,time_t));
260static int removelock P((struct hshentry*));
261static int xpandfile P((RILE*,struct hshentry const*,char const**,int));
262static struct cbuf getlogmsg P((void));
263static void cleanup P((void));
264static void incnum P((char const*,struct buf*));
265static void addassoclst P((int,char const*));
266
267static FILE *exfile;
268static RILE *workptr;			/* working file pointer		*/
269static struct buf newdelnum;		/* new revision number		*/
270static struct cbuf msg;
271static int exitstatus;
272static int forceciflag;			/* forces check in		*/
273static int keepflag, keepworkingfile, rcsinitflag;
274static struct hshentries *gendeltas;	/* deltas to be generated	*/
275static struct hshentry *targetdelta;	/* old delta to be generated	*/
276static struct hshentry newdelta;	/* new delta to be inserted	*/
277static struct stat workstat;
278static struct Symrev *assoclst, **nextassoc;
279
280mainProg(ciId, "ci", "Id: ci.c,v 5.30 1995/06/16 06:19:24 eggert 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
831cleanup()
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
846exiterr()
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
859addelta()
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
981addbranch(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
1078addsyms(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
1091incnum(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
1126removelock(delta)
1127struct 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            }
1152	if (!StrictLocks && myself(RCSstat.st_uid))
1153	    return 0;
1154	rcserror("no lock set by %s for revision %s", getcaller(), num);
1155	return -1;
1156}
1157
1158
1159
1160	static char const *
1161getcurdate()
1162/* Return a pointer to the current date.  */
1163{
1164	static char buffer[datesize]; /* date buffer */
1165
1166	if (!buffer[0])
1167		time2date(now(), buffer);
1168        return buffer;
1169}
1170
1171	static int
1172#if has_prototypes
1173fixwork(mode_t newworkmode, time_t mtime)
1174  /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1175#else
1176  fixwork(newworkmode, mtime)
1177	mode_t newworkmode;
1178	time_t mtime;
1179#endif
1180{
1181	return
1182			1 < workstat.st_nlink
1183		    ||	(newworkmode&S_IWUSR && !myself(workstat.st_uid))
1184		    ||	setmtime(workname, mtime) != 0
1185		?   -1
1186	    :	workstat.st_mode == newworkmode  ?  0
1187#if has_fchmod
1188	    :	fchmod(Ifileno(workptr), newworkmode) == 0  ?  0
1189#endif
1190#if bad_chmod_close
1191	    :	-1
1192#else
1193	    :	chmod(workname, newworkmode)
1194#endif
1195	;
1196}
1197
1198	static int
1199xpandfile(unexfile, delta, exname, dolog)
1200	RILE *unexfile;
1201	struct hshentry const *delta;
1202	char const **exname;
1203	int dolog;
1204/*
1205 * Read unexfile and copy it to a
1206 * file, performing keyword substitution with data from delta.
1207 * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
1208 * If successful, stores the stream descriptor into *EXFILEP
1209 * and its name into *EXNAME.
1210 */
1211{
1212	char const *targetname;
1213	int e, r;
1214
1215	targetname = makedirtemp(1);
1216	if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) {
1217		eerror(targetname);
1218		workerror("can't build working file");
1219		return -1;
1220        }
1221	r = 0;
1222	if (MIN_UNEXPAND <= Expand)
1223		fastcopy(unexfile,exfile);
1224	else {
1225		for (;;) {
1226			e = expandline(
1227				unexfile, exfile, delta, false, (FILE*)0, dolog
1228			);
1229			if (e < 0)
1230				break;
1231			r |= e;
1232			if (e <= 1)
1233				break;
1234		}
1235	}
1236	*exname = targetname;
1237	return r & 1;
1238}
1239
1240
1241
1242
1243/* --------------------- G E T L O G M S G --------------------------------*/
1244
1245
1246	static struct cbuf
1247getlogmsg()
1248/* Obtain and yield a log message.
1249 * If a log message is given with -m, yield that message.
1250 * If this is the initial revision, yield a standard log message.
1251 * Otherwise, reads a character string from the terminal.
1252 * Stops after reading EOF or a single '.' on a
1253 * line. getlogmsg prompts the first time it is called for the
1254 * log message; during all later calls it asks whether the previous
1255 * log message can be reused.
1256 */
1257{
1258	static char const
1259		emptych[] = EMPTYLOG,
1260		initialch[] = "Initial revision";
1261	static struct cbuf const
1262		emptylog = { emptych, sizeof(emptych)-sizeof(char) },
1263		initiallog = { initialch, sizeof(initialch)-sizeof(char) };
1264	static struct buf logbuf;
1265	static struct cbuf logmsg;
1266
1267	register char *tp;
1268	register size_t i;
1269	char const *caller;
1270
1271	if (msg.size) return msg;
1272
1273	if (keepflag) {
1274		/* generate std. log message */
1275		caller = getcaller();
1276		i = sizeof(ciklog)+strlen(caller)+3;
1277		bufalloc(&logbuf, i + datesize + zonelenmax);
1278		tp = logbuf.string;
1279		VOID sprintf(tp, "%s%s at ", ciklog, caller);
1280		VOID date2str(getcurdate(), tp+i);
1281		logmsg.string = tp;
1282		logmsg.size = strlen(tp);
1283		return logmsg;
1284	}
1285
1286	if (!targetdelta && (
1287		cmpnum(newdelnum.string,"1.1")==0 ||
1288		cmpnum(newdelnum.string,"1.0")==0
1289	))
1290		return initiallog;
1291
1292	if (logmsg.size) {
1293                /*previous log available*/
1294	    if (yesorno(true, "reuse log message of previous file? [yn](y): "))
1295		return logmsg;
1296        }
1297
1298        /* now read string from stdin */
1299	logmsg = getsstdin("m", "log message", "", &logbuf);
1300
1301        /* now check whether the log message is not empty */
1302	if (logmsg.size)
1303		return logmsg;
1304	return emptylog;
1305}
1306
1307/*  Make a linked list of Symbolic names  */
1308
1309        static void
1310addassoclst(flag, sp)
1311	int flag;
1312	char const *sp;
1313{
1314        struct Symrev *pt;
1315
1316	pt = talloc(struct Symrev);
1317	pt->ssymbol = sp;
1318	pt->override = flag;
1319	pt->nextsym = 0;
1320	*nextassoc = pt;
1321	nextassoc = &pt->nextsym;
1322}
1323