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
7This file is part of RCS.
8
9RCS is free software; you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 2, or (at your option)
12any later version.
13
14RCS is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with RCS; see the file COPYING.
21If not, write to the Free Software Foundation,
2259 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24Report 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
246struct Symrev {
247       char const *ssymbol;
248       int override;
249       struct Symrev * nextsym;
250};
251
252static char const *getcurdate P((void));
253static int addbranch P((struct hshentry*,struct buf*,int));
254static int addelta P((void));
255static int addsyms P((char const*));
256static int fixwork P((mode_t,time_t));
257static int removelock P((struct hshentry*));
258static int xpandfile P((RILE*,struct hshentry const*,char const**,int));
259static struct cbuf getlogmsg P((void));
260static void cleanup P((void));
261static void incnum P((char const*,struct buf*));
262static void addassoclst P((int,char const*));
263
264static FILE *exfile;
265static RILE *workptr;			/* working file pointer		*/
266static struct buf newdelnum;		/* new revision number		*/
267static struct cbuf msg;
268static int exitstatus;
269static int forceciflag;			/* forces check in		*/
270static int keepflag, keepworkingfile, rcsinitflag;
271static struct hshentries *gendeltas;	/* deltas to be generated	*/
272static struct hshentry *targetdelta;	/* old delta to be generated	*/
273static struct hshentry newdelta;	/* new delta to be inserted	*/
274static struct stat workstat;
275static struct Symrev *assoclst, **nextassoc;
276
277mainProg(ciId, "ci", "$FreeBSD$")
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
828cleanup()
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
843exiterr()
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
856addelta()
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
978addbranch(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
1075addsyms(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
1088incnum(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
1123removelock(delta)
1124struct 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 *
1157getcurdate()
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
1169fixwork(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
1195xpandfile(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
1243getlogmsg()
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
1306addassoclst(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