111894Speter/* Check in revisions of RCS files from working files.  */
211894Speter
311894Speter/* Copyright 1982, 1988, 1989 Walter Tichy
411894Speter   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
59Sjkh   Distributed under license by the Free Software Foundation, Inc.
69Sjkh
79SjkhThis file is part of RCS.
89Sjkh
99SjkhRCS is free software; you can redistribute it and/or modify
109Sjkhit under the terms of the GNU General Public License as published by
119Sjkhthe Free Software Foundation; either version 2, or (at your option)
129Sjkhany later version.
139Sjkh
149SjkhRCS is distributed in the hope that it will be useful,
159Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of
169SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
179SjkhGNU General Public License for more details.
189Sjkh
199SjkhYou should have received a copy of the GNU General Public License
2011894Speteralong with RCS; see the file COPYING.
2111894SpeterIf not, write to the Free Software Foundation,
2211894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
239Sjkh
249SjkhReport problems and direct all questions to:
259Sjkh
269Sjkh    rcs-bugs@cs.purdue.edu
279Sjkh
289Sjkh*/
299Sjkh
309Sjkh/*
3111894Speter * Revision 5.30  1995/06/16 06:19:24  eggert
3211894Speter * Update FSF address.
338858Srgrimes *
3411894Speter * Revision 5.29  1995/06/01 16:23:43  eggert
3511894Speter * (main): Add -kb.
3611894Speter * Use `cmpdate', not `cmpnum', to compare dates.
3711894Speter * This is for MKS RCS's incompatible 20th-century date format.
3811894Speter * Don't worry about errno after ftruncate fails.
3911894Speter * Fix input file rewinding bug when large_memory && !maps_memory
4011894Speter * and checking in a branch tip.
4111894Speter *
4211894Speter * (fixwork): Fall back on chmod if fchmod fails, since it might be ENOSYS.
4311894Speter *
4411894Speter * Revision 5.28  1994/03/20 04:52:58  eggert
4511894Speter * Do not generate a corrupted RCS file if the user modifies the working file
4611894Speter * while `ci' is running.
4711894Speter * Do not remove the lock when `ci -l' reverts.
4811894Speter * Move buffer-flushes out of critical sections, since they aren't critical.
4911894Speter * Use ORCSerror to clean up after a fatal error.
5011894Speter * Specify subprocess input via file descriptor, not file name.
5111894Speter *
5211894Speter * Revision 5.27  1993/11/09 17:40:15  eggert
5311894Speter * -V now prints version on stdout and exits.  Don't print usage twice.
5411894Speter *
5511894Speter * Revision 5.26  1993/11/03 17:42:27  eggert
5611894Speter * Add -z.  Don't subtract from RCS file timestamp even if -T.
5711894Speter * Scan for and use Name keyword if -k.
5811894Speter * Don't discard ignored phrases.  Improve quality of diagnostics.
5911894Speter *
6011894Speter * Revision 5.25  1992/07/28  16:12:44  eggert
6111894Speter * Add -i, -j, -V.  Check that working and RCS files are distinct.
6211894Speter *
6311894Speter * Revision 5.24  1992/02/17  23:02:06  eggert
6411894Speter * `-rREV' now just specifies a revision REV; only bare `-r' reverts to default.
6511894Speter * Add -T.
6611894Speter *
6711894Speter * Revision 5.23  1992/01/27  16:42:51  eggert
6811894Speter * Always unlock branchpoint if caller has a lock.
6911894Speter * Add support for bad_chmod_close, bad_creat0.  lint -> RCS_lint
7011894Speter *
7111894Speter * Revision 5.22  1992/01/06  02:42:34  eggert
7211894Speter * Invoke utime() before chmod() to keep some buggy systems happy.
7311894Speter *
749Sjkh * Revision 5.21  1991/11/20  17:58:07  eggert
759Sjkh * Don't read the delta tree from a nonexistent RCS file.
769Sjkh *
779Sjkh * Revision 5.20  1991/10/07  17:32:46  eggert
789Sjkh * Fix log bugs.  Remove lint.
799Sjkh *
809Sjkh * Revision 5.19  1991/09/26  23:10:30  eggert
819Sjkh * Plug file descriptor leak.
829Sjkh *
839Sjkh * Revision 5.18  1991/09/18  07:29:10  eggert
849Sjkh * Work around a common ftruncate() bug.
859Sjkh *
869Sjkh * Revision 5.17  1991/09/10  22:15:46  eggert
879Sjkh * Fix test for redirected stdin.
889Sjkh *
899Sjkh * Revision 5.16  1991/08/19  23:17:54  eggert
909Sjkh * When there are no changes, revert to previous revision instead of aborting.
919Sjkh * Add piece tables, -M, -r$.  Tune.
929Sjkh *
939Sjkh * Revision 5.15  1991/04/21  11:58:14  eggert
949Sjkh * Ensure that working file is newer than RCS file after ci -[lu].
959Sjkh * Add -x, RCSINIT, MS-DOS support.
969Sjkh *
979Sjkh * Revision 5.14  1991/02/28  19:18:47  eggert
989Sjkh * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first.
999Sjkh * Fix ci -ko -l mode bug.  Open work file at most once.
1009Sjkh *
1019Sjkh * Revision 5.13  1991/02/25  07:12:33  eggert
1029Sjkh * getdate -> getcurdate (SVR4 name clash)
1039Sjkh *
1049Sjkh * Revision 5.12  1990/12/31  01:00:12  eggert
1059Sjkh * Don't use uninitialized storage when handling -{N,n}.
1069Sjkh *
1079Sjkh * Revision 5.11  1990/12/04  05:18:36  eggert
1089Sjkh * Use -I for prompts and -q for diagnostics.
1099Sjkh *
1109Sjkh * Revision 5.10  1990/11/05  20:30:10  eggert
1119Sjkh * Don't remove working file when aborting due to no changes.
1129Sjkh *
1139Sjkh * Revision 5.9  1990/11/01  05:03:23  eggert
1149Sjkh * Add -I and new -t behavior.  Permit arbitrary data in logs.
1159Sjkh *
1169Sjkh * Revision 5.8  1990/10/04  06:30:09  eggert
1179Sjkh * Accumulate exit status across files.
1189Sjkh *
1199Sjkh * Revision 5.7  1990/09/25  20:11:46  hammer
1209Sjkh * fixed another small typo
1219Sjkh *
1229Sjkh * Revision 5.6  1990/09/24  21:48:50  hammer
1239Sjkh * added cleanups from Paul Eggert.
1249Sjkh *
1259Sjkh * Revision 5.5  1990/09/21  06:16:38  hammer
1269Sjkh * made it handle multiple -{N,n}'s.  Also, made it treat re-directed stdin
1279Sjkh * the same as the terminal
1289Sjkh *
1299Sjkh * Revision 5.4  1990/09/20  02:38:51  eggert
1309Sjkh * ci -k now checks dates more thoroughly.
1319Sjkh *
1329Sjkh * Revision 5.3  1990/09/11  02:41:07  eggert
1339Sjkh * Fix revision bug with `ci -k file1 file2'.
1349Sjkh *
1359Sjkh * Revision 5.2  1990/09/04  08:02:10  eggert
1369Sjkh * Permit adjacent revisions with identical time stamps (possible on fast hosts).
1379Sjkh * Improve incomplete line handling.  Standardize yes-or-no procedure.
1389Sjkh *
1399Sjkh * Revision 5.1  1990/08/29  07:13:44  eggert
1409Sjkh * Expand locker value like co.  Clean old log messages too.
1419Sjkh *
1429Sjkh * Revision 5.0  1990/08/22  08:10:00  eggert
1439Sjkh * Don't require a final newline.
1449Sjkh * Make lock and temp files faster and safer.
1459Sjkh * Remove compile-time limits; use malloc instead.
1469Sjkh * Permit dates past 1999/12/31.  Switch to GMT.
1479Sjkh * Add setuid support.  Don't pass +args to diff.  Check diff's output.
1489Sjkh * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
1499Sjkh * Check diff's output.
1509Sjkh *
1519Sjkh * Revision 4.9  89/05/01  15:10:54  narten
1529Sjkh * changed copyright header to reflect current distribution rules
1538858Srgrimes *
1549Sjkh * Revision 4.8  88/11/08  13:38:23  narten
1559Sjkh * changes from root@seismo.CSS.GOV (Super User)
1569Sjkh * -d with no arguments uses the mod time of the file it is checking in
1578858Srgrimes *
1589Sjkh * Revision 4.7  88/08/09  19:12:07  eggert
1599Sjkh * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one.
1609Sjkh * Use execv(), not system(); allow cc -R; remove lint.
1619Sjkh * isatty(fileno(stdin)) -> ttystdin()
1628858Srgrimes *
1639Sjkh * Revision 4.6  87/12/18  11:34:41  narten
1649Sjkh * lint cleanups (from Guy Harris)
1658858Srgrimes *
1669Sjkh * Revision 4.5  87/10/18  10:18:48  narten
1679Sjkh * Updating version numbers. Changes relative to revision 1.1 are actually
1689Sjkh * relative to 4.3
1698858Srgrimes *
1709Sjkh * Revision 1.3  87/09/24  13:57:19  narten
1718858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf
1729Sjkh * warnings)
1738858Srgrimes *
1749Sjkh * Revision 1.2  87/03/27  14:21:33  jenkins
1759Sjkh * Port to suns
1768858Srgrimes *
1779Sjkh * Revision 4.3  83/12/15  12:28:54  wft
1789Sjkh * ci -u and ci -l now set mode of working file properly.
1798858Srgrimes *
1809Sjkh * Revision 4.2  83/12/05  13:40:54  wft
1819Sjkh * Merged with 3.9.1.1: added calls to clearerr(stdin).
1829Sjkh * made rewriteflag external.
1838858Srgrimes *
1849Sjkh * Revision 4.1  83/05/10  17:03:06  wft
1859Sjkh * Added option -d and -w, and updated assingment of date, etc. to new delta.
1869Sjkh * Added handling of default branches.
1879Sjkh * Option -k generates std. log message; fixed undef. pointer in reading of log.
1889Sjkh * Replaced getlock() with findlock(), link--unlink with rename(),
1899Sjkh * getpwuid() with getcaller().
1909Sjkh * Moved all revision number generation to new routine addelta().
1919Sjkh * Removed calls to stat(); now done by pairfilenames().
1929Sjkh * Changed most calls to catchints() with restoreints().
1939Sjkh * Directed all interactive messages to stderr.
1948858Srgrimes *
1959Sjkh * Revision 3.9.1.1  83/10/19  04:21:03  lepreau
1969Sjkh * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
1978858Srgrimes *
1989Sjkh * Revision 3.9  83/02/15  15:25:44  wft
1999Sjkh * 4.2 prerelease
2008858Srgrimes *
2019Sjkh * Revision 3.9  83/02/15  15:25:44  wft
2029Sjkh * Added call to fastcopy() to copy remainder of RCS file.
2039Sjkh *
2049Sjkh * Revision 3.8  83/01/14  15:34:05  wft
2059Sjkh * Added ignoring of interrupts while new RCS file is renamed;
2069Sjkh * Avoids deletion of RCS files by interrupts.
2079Sjkh *
2089Sjkh * Revision 3.7  82/12/10  16:09:20  wft
2099Sjkh * Corrected checking of return code from diff.
2109Sjkh *
2119Sjkh * Revision 3.6  82/12/08  21:34:49  wft
2129Sjkh * Using DATEFORM to prepare date of checked-in revision;
2139Sjkh * Fixed return from addbranch().
2149Sjkh *
2159Sjkh * Revision 3.5  82/12/04  18:32:42  wft
2169Sjkh * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated
2179Sjkh * field lockedby in removelock(), moved getlogmsg() before calling diff.
2189Sjkh *
2199Sjkh * Revision 3.4  82/12/02  13:27:13  wft
2209Sjkh * added option -k.
2219Sjkh *
2229Sjkh * Revision 3.3  82/11/28  20:53:31  wft
2239Sjkh * Added mustcheckin() to check for redundant checkins.
2249Sjkh * Added xpandfile() to do keyword expansion for -u and -l;
2259Sjkh * -m appends linefeed to log message if necessary.
2269Sjkh * getlogmsg() suppresses prompt if stdin is not a terminal.
2279Sjkh * Replaced keeplock with lockflag, fclose() with ffclose(),
2289Sjkh * %02d with %.2d, getlogin() with getpwuid().
2299Sjkh *
2309Sjkh * Revision 3.2  82/10/18  20:57:23  wft
2319Sjkh * An RCS file inherits its mode during the first ci from the working file,
2329Sjkh * otherwise it stays the same, except that write permission is removed.
2339Sjkh * Fixed ci -l, added ci -u (both do an implicit co after the ci).
2349Sjkh * Fixed call to getlogin(), added call to getfullRCSname(), added check
2359Sjkh * for write error.
2369Sjkh * Changed conflicting identifiers.
2379Sjkh *
2389Sjkh * Revision 3.1  82/10/13  16:04:59  wft
2399Sjkh * fixed type of variables receiving from getc() (char -> int).
2409Sjkh * added include file dbm.h for getting BYTESIZ. This is used
2419Sjkh * to check the return code from diff portably.
2429Sjkh */
2439Sjkh
2449Sjkh#include "rcsbase.h"
2459Sjkh
2469Sjkhstruct Symrev {
2479Sjkh       char const *ssymbol;
2489Sjkh       int override;
2499Sjkh       struct Symrev * nextsym;
2509Sjkh};
2519Sjkh
2529Sjkhstatic char const *getcurdate P((void));
25311894Speterstatic int addbranch P((struct hshentry*,struct buf*,int));
2549Sjkhstatic int addelta P((void));
2559Sjkhstatic int addsyms P((char const*));
25611894Speterstatic int fixwork P((mode_t,time_t));
2579Sjkhstatic int removelock P((struct hshentry*));
25811894Speterstatic int xpandfile P((RILE*,struct hshentry const*,char const**,int));
2599Sjkhstatic struct cbuf getlogmsg P((void));
2609Sjkhstatic void cleanup P((void));
2619Sjkhstatic void incnum P((char const*,struct buf*));
26211894Speterstatic void addassoclst P((int,char const*));
2639Sjkh
2649Sjkhstatic FILE *exfile;
2659Sjkhstatic RILE *workptr;			/* working file pointer		*/
2669Sjkhstatic struct buf newdelnum;		/* new revision number		*/
2679Sjkhstatic struct cbuf msg;
2689Sjkhstatic int exitstatus;
2699Sjkhstatic int forceciflag;			/* forces check in		*/
2709Sjkhstatic int keepflag, keepworkingfile, rcsinitflag;
2719Sjkhstatic struct hshentries *gendeltas;	/* deltas to be generated	*/
2729Sjkhstatic struct hshentry *targetdelta;	/* old delta to be generated	*/
2739Sjkhstatic struct hshentry newdelta;	/* new delta to be inserted	*/
2749Sjkhstatic struct stat workstat;
27511894Speterstatic struct Symrev *assoclst, **nextassoc;
2769Sjkh
27750472SpetermainProg(ciId, "ci", "$FreeBSD$")
2789Sjkh{
2799Sjkh	static char const cmdusage[] =
28011894Speter		"\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -T -Vn -wwho -xsuff -zzone file ...";
2819Sjkh	static char const default_state[] = DEFAULTSTATE;
2829Sjkh
2839Sjkh	char altdate[datesize];
2849Sjkh	char olddate[datesize];
28511894Speter	char newdatebuf[datesize + zonelenmax];
28611894Speter	char targetdatebuf[datesize + zonelenmax];
2879Sjkh	char *a, **newargv, *textfile;
2889Sjkh	char const *author, *krev, *rev, *state;
28911894Speter	char const *diffname, *expname;
29011894Speter	char const *newworkname;
29111894Speter	int initflag, mustread;
29211894Speter	int lockflag, lockthis, mtimeflag, removedlock, Ttimeflag;
2939Sjkh	int r;
29411894Speter	int changedRCS, changework, dolog, newhead;
2959Sjkh	int usestatdate; /* Use mod time of file for -d.  */
2969Sjkh	mode_t newworkmode; /* mode for working file */
29711894Speter	time_t mtime, wtime;
2989Sjkh	struct hshentry *workdelta;
2998858Srgrimes
3009Sjkh	setrid();
3019Sjkh
30211894Speter	author = rev = state = textfile = 0;
30311894Speter	initflag = lockflag = mustread = false;
3049Sjkh	mtimeflag = false;
30511894Speter	Ttimeflag = false;
3069Sjkh	altdate[0]= '\0'; /* empty alternate date for -d */
3079Sjkh	usestatdate=false;
3089Sjkh	suffixes = X_DEFAULT;
30911894Speter	nextassoc = &assoclst;
3109Sjkh
3119Sjkh	argc = getRCSINIT(argc, argv, &newargv);
3129Sjkh	argv = newargv;
3139Sjkh	while (a = *++argv,  0<--argc && *a++=='-') {
3149Sjkh		switch (*a++) {
3159Sjkh
3169Sjkh                case 'r':
31711894Speter			if (*a)
31811894Speter				goto revno;
3199Sjkh			keepworkingfile = lockflag = false;
32011894Speter			break;
32111894Speter
32211894Speter		case 'l':
32311894Speter			keepworkingfile = lockflag = true;
3249Sjkh		revno:
3259Sjkh			if (*a) {
3269Sjkh				if (rev) warn("redefinition of revision number");
3279Sjkh				rev = a;
3289Sjkh                        }
3299Sjkh                        break;
3309Sjkh
3319Sjkh                case 'u':
3329Sjkh                        keepworkingfile=true; lockflag=false;
3339Sjkh                        goto revno;
3349Sjkh
33511894Speter		case 'i':
33611894Speter			initflag = true;
33711894Speter			goto revno;
33811894Speter
33911894Speter		case 'j':
34011894Speter			mustread = true;
34111894Speter			goto revno;
34211894Speter
3439Sjkh		case 'I':
3449Sjkh			interactiveflag = true;
3459Sjkh			goto revno;
3469Sjkh
3479Sjkh                case 'q':
3489Sjkh                        quietflag=true;
3499Sjkh                        goto revno;
3509Sjkh
3519Sjkh                case 'f':
3529Sjkh                        forceciflag=true;
3539Sjkh                        goto revno;
3549Sjkh
3559Sjkh                case 'k':
3569Sjkh                        keepflag=true;
3579Sjkh                        goto revno;
3589Sjkh
3599Sjkh                case 'm':
3609Sjkh			if (msg.size) redefined('m');
3619Sjkh			msg = cleanlogmsg(a, strlen(a));
3629Sjkh			if (!msg.size)
36311894Speter				error("missing message for -m option");
3649Sjkh                        break;
3659Sjkh
3669Sjkh                case 'n':
3679Sjkh			if (!*a) {
3689Sjkh                                error("missing symbolic name after -n");
3699Sjkh				break;
3709Sjkh            		}
37111894Speter			checkssym(a);
3729Sjkh			addassoclst(false, a);
3739Sjkh		        break;
3748858Srgrimes
3759Sjkh		case 'N':
3769Sjkh			if (!*a) {
3779Sjkh                                error("missing symbolic name after -N");
3789Sjkh				break;
3799Sjkh            		}
38011894Speter			checkssym(a);
3819Sjkh			addassoclst(true, a);
3829Sjkh		        break;
3839Sjkh
3849Sjkh                case 's':
3859Sjkh			if (*a) {
3869Sjkh				if (state) redefined('s');
3879Sjkh				checksid(a);
3889Sjkh				state = a;
3899Sjkh			} else
39011894Speter				error("missing state for -s option");
3919Sjkh                        break;
3929Sjkh
3939Sjkh                case 't':
3949Sjkh			if (*a) {
3959Sjkh				if (textfile) redefined('t');
3969Sjkh				textfile = a;
3979Sjkh                        }
3989Sjkh                        break;
3999Sjkh
4009Sjkh		case 'd':
4019Sjkh			if (altdate[0] || usestatdate)
4029Sjkh				redefined('d');
40311894Speter			altdate[0] = '\0';
4049Sjkh			if (!(usestatdate = !*a))
4059Sjkh				str2date(a, altdate);
4069Sjkh                        break;
4079Sjkh
4089Sjkh		case 'M':
4099Sjkh			mtimeflag = true;
4109Sjkh			goto revno;
4119Sjkh
4129Sjkh		case 'w':
4139Sjkh			if (*a) {
4149Sjkh				if (author) redefined('w');
4159Sjkh				checksid(a);
4169Sjkh				author = a;
4179Sjkh			} else
41811894Speter				error("missing author for -w option");
4199Sjkh                        break;
4209Sjkh
4219Sjkh		case 'x':
4229Sjkh			suffixes = a;
4239Sjkh			break;
4249Sjkh
4259Sjkh		case 'V':
4269Sjkh			setRCSversion(*argv);
4279Sjkh			break;
4289Sjkh
42911894Speter		case 'z':
43011894Speter			zone_set(a);
43111894Speter			break;
4329Sjkh
43311894Speter		case 'T':
43411894Speter			if (!*a) {
43511894Speter				Ttimeflag = true;
43611894Speter				break;
43711894Speter			}
43811894Speter			/* fall into */
4399Sjkh                default:
44011894Speter			error("unknown option: %s%s", *argv, cmdusage);
4419Sjkh                };
4429Sjkh        }  /* end processing of options */
4439Sjkh
44411894Speter	/* Handle all pathnames.  */
44511894Speter	if (nerror) cleanup();
44611894Speter	else if (argc < 1) faterror("no input file%s", cmdusage);
44711894Speter	else for (;  0 < argc;  cleanup(), ++argv, --argc) {
44811894Speter	targetdelta = 0;
4499Sjkh	ffree();
4509Sjkh
45111894Speter	switch (pairnames(argc, argv, rcswriteopen, mustread, false)) {
4529Sjkh
4539Sjkh        case -1:                /* New RCS file */
4549Sjkh#		if has_setuid && has_getuid
4559Sjkh		    if (euid() != ruid()) {
45611894Speter			workerror("setuid initial checkin prohibited; use `rcs -i -a' first");
4579Sjkh			continue;
4589Sjkh		    }
4599Sjkh#		endif
4609Sjkh		rcsinitflag = true;
4619Sjkh                break;
4629Sjkh
4639Sjkh        case 0:                 /* Error */
4649Sjkh                continue;
4659Sjkh
4669Sjkh        case 1:                 /* Normal checkin with prev . RCS file */
46711894Speter		if (initflag) {
46811894Speter			rcserror("already exists");
46911894Speter			continue;
47011894Speter		}
4719Sjkh		rcsinitflag = !Head;
4729Sjkh        }
4739Sjkh
47411894Speter	/*
47511894Speter	 * RCSname contains the name of the RCS file, and
47611894Speter	 * workname contains the name of the working file.
4779Sjkh	 * If the RCS file exists, finptr contains the file descriptor for the
47811894Speter	 * RCS file, and RCSstat is set. The admin node is initialized.
4799Sjkh         */
4809Sjkh
48111894Speter	diagnose("%s  <--  %s\n", RCSname, workname);
4829Sjkh
48311894Speter	if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) {
48411894Speter		eerror(workname);
4859Sjkh		continue;
4869Sjkh	}
4879Sjkh
48811894Speter	if (finptr) {
48911894Speter		if (same_file(RCSstat, workstat, 0)) {
49011894Speter			rcserror("RCS file is the same as working file %s.",
49111894Speter				workname
49211894Speter			);
49311894Speter			continue;
49411894Speter		}
49511894Speter		if (!checkaccesslist())
49611894Speter			continue;
49711894Speter	}
49811894Speter
4999Sjkh	krev = rev;
5009Sjkh        if (keepflag) {
5019Sjkh                /* get keyword values from working file */
5029Sjkh		if (!getoldkeys(workptr)) continue;
5039Sjkh		if (!rev  &&  !*(krev = prevrev.string)) {
50411894Speter			workerror("can't find a revision number");
5059Sjkh                        continue;
5069Sjkh                }
5079Sjkh		if (!*prevdate.string && *altdate=='\0' && usestatdate==false)
50811894Speter			workwarn("can't find a date");
5099Sjkh		if (!*prevauthor.string && !author)
51011894Speter			workwarn("can't find an author");
5119Sjkh		if (!*prevstate.string && !state)
51211894Speter			workwarn("can't find a state");
5139Sjkh        } /* end processing keepflag */
5149Sjkh
5159Sjkh	/* Read the delta tree.  */
5169Sjkh	if (finptr)
5179Sjkh	    gettree();
5189Sjkh
5199Sjkh        /* expand symbolic revision number */
5209Sjkh	if (!fexpandsym(krev, &newdelnum, workptr))
5219Sjkh	    continue;
5229Sjkh
5239Sjkh        /* splice new delta into tree */
5249Sjkh	if ((removedlock = addelta()) < 0)
5259Sjkh	    continue;
5269Sjkh
5279Sjkh	newdelta.num = newdelnum.string;
52811894Speter	newdelta.branches = 0;
52911894Speter	newdelta.lockedby = 0; /* This might be changed by addlock().  */
5309Sjkh	newdelta.selector = true;
53111894Speter	newdelta.name = 0;
53211894Speter	clear_buf(&newdelta.ig);
53311894Speter	clear_buf(&newdelta.igtext);
5349Sjkh	/* set author */
53511894Speter	if (author)
5369Sjkh		newdelta.author=author;     /* set author given by -w         */
5379Sjkh	else if (keepflag && *prevauthor.string)
5389Sjkh		newdelta.author=prevauthor.string; /* preserve old author if possible*/
5399Sjkh	else    newdelta.author=getcaller();/* otherwise use caller's id      */
5409Sjkh	newdelta.state = default_state;
54111894Speter	if (state)
5429Sjkh		newdelta.state=state;       /* set state given by -s          */
5439Sjkh	else if (keepflag && *prevstate.string)
5449Sjkh		newdelta.state=prevstate.string;   /* preserve old state if possible */
5459Sjkh	if (usestatdate) {
5469Sjkh	    time2date(workstat.st_mtime, altdate);
5479Sjkh	}
5489Sjkh	if (*altdate!='\0')
5499Sjkh		newdelta.date=altdate;      /* set date given by -d           */
5509Sjkh	else if (keepflag && *prevdate.string) {
5519Sjkh		/* Preserve old date if possible.  */
5529Sjkh		str2date(prevdate.string, olddate);
5539Sjkh		newdelta.date = olddate;
5549Sjkh	} else
5559Sjkh		newdelta.date = getcurdate();  /* use current date */
5569Sjkh	/* now check validity of date -- needed because of -d and -k          */
55711894Speter	if (targetdelta &&
55811894Speter	    cmpdate(newdelta.date,targetdelta->date) < 0) {
55911894Speter		rcserror("Date %s precedes %s in revision %s.",
5609Sjkh			date2str(newdelta.date, newdatebuf),
5619Sjkh			date2str(targetdelta->date, targetdatebuf),
5629Sjkh			targetdelta->num
5639Sjkh		);
5649Sjkh		continue;
5659Sjkh	}
5669Sjkh
5679Sjkh
56811894Speter	if (lockflag  &&  addlock(&newdelta, true) < 0) continue;
56911894Speter
57011894Speter	if (keepflag && *prevname.string)
57111894Speter	    if (addsymbol(newdelta.num, prevname.string, false)  <  0)
57211894Speter		continue;
5739Sjkh	if (!addsyms(newdelta.num))
5749Sjkh	    continue;
5759Sjkh
5768858Srgrimes
57711894Speter	putadmin();
5789Sjkh        puttree(Head,frewrite);
5799Sjkh	putdesc(false,textfile);
5809Sjkh
58111894Speter	changework = Expand < MIN_UNCHANGED_EXPAND;
58211894Speter	dolog = true;
5839Sjkh	lockthis = lockflag;
5849Sjkh	workdelta = &newdelta;
5859Sjkh
5869Sjkh        /* build rest of file */
5879Sjkh	if (rcsinitflag) {
58811894Speter		diagnose("initial revision: %s\n", newdelta.num);
5899Sjkh                /* get logmessage */
5909Sjkh                newdelta.log=getlogmsg();
59111894Speter		putdftext(&newdelta, workptr, frewrite, false);
5929Sjkh		RCSstat.st_mode = workstat.st_mode;
59311894Speter		RCSstat.st_nlink = 0;
5949Sjkh		changedRCS = true;
5959Sjkh        } else {
59611894Speter		diffname = maketemp(0);
5979Sjkh		newhead  =  Head == &newdelta;
5989Sjkh		if (!newhead)
5999Sjkh			foutptr = frewrite;
60011894Speter		expname = buildrevision(
6019Sjkh			gendeltas, targetdelta, (FILE*)0, false
6029Sjkh		);
6039Sjkh		if (
6049Sjkh		    !forceciflag  &&
60511894Speter		    strcmp(newdelta.state, targetdelta->state) == 0  &&
6069Sjkh		    (changework = rcsfcmp(
60711894Speter			workptr, &workstat, expname, targetdelta
6089Sjkh		    )) <= 0
6099Sjkh		) {
6109Sjkh		    diagnose("file is unchanged; reverting to previous revision %s\n",
6119Sjkh			targetdelta->num
6129Sjkh		    );
6139Sjkh		    if (removedlock < lockflag) {
6149Sjkh			diagnose("previous revision was not locked; ignoring -l option\n");
6159Sjkh			lockthis = 0;
6169Sjkh		    }
61711894Speter		    dolog = false;
61811894Speter		    if (! (changedRCS = lockflag<removedlock || assoclst))
6199Sjkh			workdelta = targetdelta;
6209Sjkh		    else {
6219Sjkh			/*
6229Sjkh			 * We have started to build the wrong new RCS file.
6239Sjkh			 * Start over from the beginning.
6249Sjkh			 */
6259Sjkh			long hwm = ftell(frewrite);
6269Sjkh			int bad_truncate;
62711894Speter			Orewind(frewrite);
62811894Speter
62911894Speter			/*
63011894Speter			* Work around a common ftruncate() bug:
63111894Speter			* NFS won't let you truncate a file that you
63211894Speter			* currently lack permissions for, even if you
63311894Speter			* had permissions when you opened it.
63411894Speter			* Also, Posix 1003.1b-1993 sec 5.6.7.2 p 128 l 1022
63511894Speter			* says ftruncate might fail because it's not supported.
63611894Speter			*/
6379Sjkh#			if !has_ftruncate
63811894Speter#			    undef ftruncate
63911894Speter#			    define ftruncate(fd,length) (-1)
6409Sjkh#			endif
64111894Speter			bad_truncate = ftruncate(fileno(frewrite), (off_t)0);
64211894Speter
6439Sjkh			Irewind(finptr);
6449Sjkh			Lexinit();
6459Sjkh			getadmin();
6469Sjkh			gettree();
6479Sjkh			if (!(workdelta = genrevs(
6489Sjkh			    targetdelta->num, (char*)0, (char*)0, (char*)0,
6499Sjkh			    &gendeltas
6509Sjkh			)))
6519Sjkh			    continue;
6529Sjkh			workdelta->log = targetdelta->log;
6539Sjkh			if (newdelta.state != default_state)
6549Sjkh			    workdelta->state = newdelta.state;
65511894Speter			if (lockthis<removedlock && removelock(workdelta)<0)
6569Sjkh			    continue;
6579Sjkh			if (!addsyms(workdelta->num))
6589Sjkh			    continue;
65911894Speter			if (dorewrite(true, true) != 0)
6609Sjkh			    continue;
6619Sjkh			fastcopy(finptr, frewrite);
6629Sjkh			if (bad_truncate)
6639Sjkh			    while (ftell(frewrite) < hwm)
6649Sjkh				/* White out any earlier mistake with '\n's.  */
6659Sjkh				/* This is unlikely.  */
6669Sjkh				afputc('\n', frewrite);
6679Sjkh		    }
6689Sjkh		} else {
66911894Speter		    int wfd = Ifileno(workptr);
67011894Speter		    struct stat checkworkstat;
67111894Speter		    char const *diffv[6 + !!OPEN_O_BINARY], **diffp;
67211894Speter#		    if large_memory && !maps_memory
67311894Speter			FILE *wfile = workptr->stream;
67411894Speter			long wfile_off;
67511894Speter#		    endif
67611894Speter#		    if !has_fflush_input && !(large_memory && maps_memory)
67711894Speter		        off_t wfd_off;
67811894Speter#		    endif
67911894Speter
6809Sjkh		    diagnose("new revision: %s; previous revision: %s\n",
68111894Speter			newdelta.num, targetdelta->num
6829Sjkh		    );
6839Sjkh		    newdelta.log = getlogmsg();
68411894Speter#		    if !large_memory
68511894Speter			Irewind(workptr);
68611894Speter#			if has_fflush_input
68711894Speter			    if (fflush(workptr) != 0)
68811894Speter				Ierror();
68911894Speter#			endif
69011894Speter#		    else
69111894Speter#			if !maps_memory
69211894Speter			    if (
69311894Speter			    	(wfile_off = ftell(wfile)) == -1
69411894Speter			     ||	fseek(wfile, 0L, SEEK_SET) != 0
69511894Speter#			     if has_fflush_input
69611894Speter			     ||	fflush(wfile) != 0
69711894Speter#			     endif
69811894Speter			    )
69911894Speter				Ierror();
70011894Speter#			endif
70111894Speter#		    endif
70211894Speter#		    if !has_fflush_input && !(large_memory && maps_memory)
70311894Speter			wfd_off = lseek(wfd, (off_t)0, SEEK_CUR);
70411894Speter			if (wfd_off == -1
70511894Speter			    || (wfd_off != 0
70611894Speter				&& lseek(wfd, (off_t)0, SEEK_SET) != 0))
70711894Speter			    Ierror();
70811894Speter#		    endif
70911894Speter		    diffp = diffv;
71011894Speter		    *++diffp = DIFF;
71111894Speter		    *++diffp = DIFFFLAGS;
71211894Speter#		    if OPEN_O_BINARY
71311894Speter			if (Expand == BINARY_EXPAND)
71411894Speter			    *++diffp = "--binary";
71511894Speter#		    endif
71611894Speter		    *++diffp = newhead ? "-" : expname;
71711894Speter		    *++diffp = newhead ? expname : "-";
71811894Speter		    *++diffp = 0;
71911894Speter		    switch (runv(wfd, diffname, diffv)) {
7209Sjkh			case DIFF_FAILURE: case DIFF_SUCCESS: break;
72111894Speter			default: rcsfaterror("diff failed");
7229Sjkh		    }
72311894Speter#		    if !has_fflush_input && !(large_memory && maps_memory)
72411894Speter			if (lseek(wfd, wfd_off, SEEK_CUR) == -1)
72511894Speter			    Ierror();
72611894Speter#		    endif
72711894Speter#		    if large_memory && !maps_memory
72811894Speter			if (fseek(wfile, wfile_off, SEEK_SET) != 0)
72911894Speter			    Ierror();
73011894Speter#		    endif
7319Sjkh		    if (newhead) {
7329Sjkh			Irewind(workptr);
73311894Speter			putdftext(&newdelta, workptr, frewrite, false);
73411894Speter			if (!putdtext(targetdelta,diffname,frewrite,true)) continue;
7359Sjkh		    } else
73611894Speter			if (!putdtext(&newdelta,diffname,frewrite,true)) continue;
73711894Speter
73811894Speter		    /*
73911894Speter		    * Check whether the working file changed during checkin,
74011894Speter		    * to avoid producing an inconsistent RCS file.
74111894Speter		    */
74211894Speter		    if (
74311894Speter			fstat(wfd, &checkworkstat) != 0
74411894Speter		     ||	workstat.st_mtime != checkworkstat.st_mtime
74511894Speter		     ||	workstat.st_size != checkworkstat.st_size
74611894Speter		    ) {
74711894Speter			workerror("file changed during checkin");
74811894Speter			continue;
74911894Speter		    }
75011894Speter
7519Sjkh		    changedRCS = true;
7529Sjkh                }
7539Sjkh        }
75411894Speter
75511894Speter	/* Deduce time_t of new revision if it is needed later.  */
75611894Speter	wtime = (time_t)-1;
75711894Speter	if (mtimeflag | Ttimeflag)
75811894Speter		wtime = date2time(workdelta->date);
75911894Speter
76011894Speter	if (donerewrite(changedRCS,
76111894Speter		!Ttimeflag ? (time_t)-1
76211894Speter		: finptr && wtime < RCSstat.st_mtime ? RCSstat.st_mtime
76311894Speter		: wtime
76411894Speter	) != 0)
7659Sjkh		continue;
7669Sjkh
7679Sjkh        if (!keepworkingfile) {
7689Sjkh		Izclose(&workptr);
76911894Speter		r = un_link(workname); /* Get rid of old file */
7709Sjkh        } else {
7719Sjkh		newworkmode = WORKMODE(RCSstat.st_mode,
7729Sjkh			!   (Expand==VAL_EXPAND  ||  lockthis < StrictLocks)
7739Sjkh		);
77411894Speter		mtime = mtimeflag ? wtime : (time_t)-1;
7759Sjkh
7769Sjkh		/* Expand if it might change or if we can't fix mode, time.  */
7779Sjkh		if (changework  ||  (r=fixwork(newworkmode,mtime)) != 0) {
7789Sjkh		    Irewind(workptr);
7799Sjkh		    /* Expand keywords in file.  */
7809Sjkh		    locker_expansion = lockthis;
78111894Speter		    workdelta->name =
78211894Speter			namedrev(
78311894Speter				assoclst ? assoclst->ssymbol
78411894Speter				: keepflag && *prevname.string ? prevname.string
78511894Speter				: rev,
78611894Speter				workdelta
78711894Speter			);
7889Sjkh		    switch (xpandfile(
78911894Speter			workptr, workdelta, &newworkname, dolog
7909Sjkh		    )) {
7919Sjkh			default:
7929Sjkh			    continue;
7939Sjkh
7949Sjkh			case 0:
7959Sjkh			    /*
7969Sjkh			     * No expansion occurred; try to reuse working file
7979Sjkh			     * unless we already tried and failed.
7989Sjkh			     */
7999Sjkh			    if (changework)
8009Sjkh				if ((r=fixwork(newworkmode,mtime)) == 0)
8019Sjkh				    break;
8029Sjkh			    /* fall into */
8039Sjkh			case 1:
80411894Speter			    Izclose(&workptr);
80511894Speter			    aflush(exfile);
80611894Speter			    ignoreints();
80711894Speter			    r = chnamemod(&exfile, newworkname,
80811894Speter				    workname, 1, newworkmode, mtime
80911894Speter			    );
81011894Speter			    keepdirtemp(newworkname);
81111894Speter			    restoreints();
8129Sjkh		    }
8139Sjkh		}
8149Sjkh        }
8159Sjkh	if (r != 0) {
81611894Speter	    eerror(workname);
8179Sjkh	    continue;
8189Sjkh	}
8199Sjkh	diagnose("done\n");
8209Sjkh
82111894Speter	}
8229Sjkh
8239Sjkh	tempunlink();
8249Sjkh	exitmain(exitstatus);
8259Sjkh}       /* end of main (ci) */
8269Sjkh
8279Sjkh	static void
8289Sjkhcleanup()
8299Sjkh{
8309Sjkh	if (nerror) exitstatus = EXIT_FAILURE;
8319Sjkh	Izclose(&finptr);
8329Sjkh	Izclose(&workptr);
8339Sjkh	Ozclose(&exfile);
8349Sjkh	Ozclose(&fcopy);
83511894Speter	ORCSclose();
8369Sjkh	dirtempunlink();
8379Sjkh}
8389Sjkh
83911894Speter#if RCS_lint
8409Sjkh#	define exiterr ciExit
8419Sjkh#endif
84211894Speter	void
8439Sjkhexiterr()
8449Sjkh{
84511894Speter	ORCSerror();
8469Sjkh	dirtempunlink();
8479Sjkh	tempunlink();
8489Sjkh	_exit(EXIT_FAILURE);
8499Sjkh}
8509Sjkh
8519Sjkh/*****************************************************************/
8529Sjkh/* the rest are auxiliary routines                               */
8539Sjkh
8549Sjkh
8559Sjkh	static int
8569Sjkhaddelta()
8579Sjkh/* Function: Appends a delta to the delta tree, whose number is
8589Sjkh * given by newdelnum.  Updates Head, newdelnum, newdelnumlength,
8599Sjkh * and the links in newdelta.
8609Sjkh * Return -1 on error, 1 if a lock is removed, 0 otherwise.
8619Sjkh */
8629Sjkh{
8639Sjkh	register char *tp;
86411894Speter	register int i;
8659Sjkh	int removedlock;
86611894Speter	int newdnumlength;  /* actual length of new rev. num. */
8679Sjkh
8689Sjkh	newdnumlength = countnumflds(newdelnum.string);
8699Sjkh
8709Sjkh	if (rcsinitflag) {
8719Sjkh                /* this covers non-existing RCS file and a file initialized with rcs -i */
87211894Speter		if (newdnumlength==0 && Dbranch) {
8739Sjkh			bufscpy(&newdelnum, Dbranch);
8749Sjkh			newdnumlength = countnumflds(Dbranch);
8759Sjkh		}
8769Sjkh		if (newdnumlength==0) bufscpy(&newdelnum, "1.1");
8779Sjkh		else if (newdnumlength==1) bufscat(&newdelnum, ".1");
8789Sjkh		else if (newdnumlength>2) {
87911894Speter		    rcserror("Branch point doesn't exist for revision %s.",
88011894Speter			newdelnum.string
88111894Speter		    );
8829Sjkh		    return -1;
8839Sjkh                } /* newdnumlength == 2 is OK;  */
8849Sjkh                Head = &newdelta;
88511894Speter		newdelta.next = 0;
8869Sjkh		return 0;
8879Sjkh        }
8889Sjkh        if (newdnumlength==0) {
8899Sjkh                /* derive new revision number from locks */
8909Sjkh		switch (findlock(true, &targetdelta)) {
8919Sjkh
8929Sjkh		  default:
8939Sjkh		    /* found two or more old locks */
8949Sjkh		    return -1;
8959Sjkh
8969Sjkh		  case 1:
8979Sjkh                    /* found an old lock */
8989Sjkh                    /* check whether locked revision exists */
8999Sjkh		    if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas))
9009Sjkh			return -1;
9019Sjkh                    if (targetdelta==Head) {
9029Sjkh                        /* make new head */
9039Sjkh                        newdelta.next=Head;
9049Sjkh                        Head= &newdelta;
9059Sjkh		    } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) {
9069Sjkh                        /* new tip revision on side branch */
9079Sjkh                        targetdelta->next= &newdelta;
90811894Speter			newdelta.next = 0;
9099Sjkh                    } else {
9109Sjkh                        /* middle revision; start a new branch */
9119Sjkh			bufscpy(&newdelnum, "");
91211894Speter			return addbranch(targetdelta, &newdelnum, 1);
9139Sjkh                    }
9149Sjkh		    incnum(targetdelta->num, &newdelnum);
9159Sjkh		    return 1; /* successful use of existing lock */
9169Sjkh
9179Sjkh		  case 0:
9189Sjkh                    /* no existing lock; try Dbranch */
9199Sjkh                    /* update newdelnum */
9209Sjkh		    if (StrictLocks || !myself(RCSstat.st_uid)) {
92111894Speter			rcserror("no lock set by %s", getcaller());
9229Sjkh			return -1;
9239Sjkh                    }
9249Sjkh                    if (Dbranch) {
9259Sjkh			bufscpy(&newdelnum, Dbranch);
9269Sjkh                    } else {
9279Sjkh			incnum(Head->num, &newdelnum);
9289Sjkh                    }
9299Sjkh		    newdnumlength = countnumflds(newdelnum.string);
9309Sjkh                    /* now fall into next statement */
9319Sjkh                }
9329Sjkh        }
9339Sjkh        if (newdnumlength<=2) {
9349Sjkh                /* add new head per given number */
9359Sjkh                if(newdnumlength==1) {
9369Sjkh                    /* make a two-field number out of it*/
9379Sjkh		    if (cmpnumfld(newdelnum.string,Head->num,1)==0)
9389Sjkh			incnum(Head->num, &newdelnum);
9399Sjkh		    else
9409Sjkh			bufscat(&newdelnum, ".1");
9419Sjkh                }
9429Sjkh		if (cmpnum(newdelnum.string,Head->num) <= 0) {
94311894Speter		    rcserror("revision %s too low; must be higher than %s",
94411894Speter			  newdelnum.string, Head->num
94511894Speter		    );
9469Sjkh		    return -1;
9479Sjkh                }
9489Sjkh		targetdelta = Head;
9499Sjkh		if (0 <= (removedlock = removelock(Head))) {
9509Sjkh		    if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas))
9519Sjkh			return -1;
9529Sjkh		    newdelta.next = Head;
9539Sjkh		    Head = &newdelta;
9549Sjkh		}
9559Sjkh		return removedlock;
9569Sjkh        } else {
9579Sjkh                /* put new revision on side branch */
9589Sjkh                /*first, get branch point */
9599Sjkh		tp = newdelnum.string;
96011894Speter		for (i = newdnumlength - ((newdnumlength&1) ^ 1);  --i;  )
9619Sjkh			while (*tp++ != '.')
96211894Speter				continue;
9639Sjkh		*--tp = 0; /* Kill final dot to get old delta temporarily. */
96411894Speter		if (!(targetdelta=genrevs(newdelnum.string,(char*)0,(char*)0,(char*)0,&gendeltas)))
9659Sjkh		    return -1;
9669Sjkh		if (cmpnum(targetdelta->num, newdelnum.string) != 0) {
96711894Speter		    rcserror("can't find branch point %s", newdelnum.string);
9689Sjkh		    return -1;
9699Sjkh                }
9709Sjkh		*tp = '.'; /* Restore final dot. */
97111894Speter		return addbranch(targetdelta, &newdelnum, 0);
9729Sjkh        }
9739Sjkh}
9749Sjkh
9759Sjkh
9769Sjkh
9779Sjkh	static int
97811894Speteraddbranch(branchpoint, num, removedlock)
9799Sjkh	struct hshentry *branchpoint;
9809Sjkh	struct buf *num;
98111894Speter	int removedlock;
9829Sjkh/* adds a new branch and branch delta at branchpoint.
9839Sjkh * If num is the null string, appends the new branch, incrementing
9849Sjkh * the highest branch number (initially 1), and setting the level number to 1.
9859Sjkh * the new delta and branchhead are in globals newdelta and newbranch, resp.
9869Sjkh * the new number is placed into num.
9879Sjkh * Return -1 on error, 1 if a lock is removed, 0 otherwise.
98811894Speter * If REMOVEDLOCK is 1, a lock was already removed.
9899Sjkh */
9909Sjkh{
9919Sjkh	struct branchhead *bhead, **btrail;
9929Sjkh	struct buf branchnum;
99311894Speter	int result;
99411894Speter	int field, numlength;
9959Sjkh	static struct branchhead newbranch;  /* new branch to be inserted */
9969Sjkh
9979Sjkh	numlength = countnumflds(num->string);
9989Sjkh
99911894Speter	if (!branchpoint->branches) {
10009Sjkh                /* start first branch */
10019Sjkh                branchpoint->branches = &newbranch;
10029Sjkh                if (numlength==0) {
10039Sjkh			bufscpy(num, branchpoint->num);
10049Sjkh			bufscat(num, ".1.1");
10059Sjkh		} else if (numlength&1)
10069Sjkh			bufscat(num, ".1");
100711894Speter		newbranch.nextbranch = 0;
10089Sjkh
10099Sjkh	} else if (numlength==0) {
10109Sjkh                /* append new branch to the end */
10119Sjkh                bhead=branchpoint->branches;
10129Sjkh                while (bhead->nextbranch) bhead=bhead->nextbranch;
10139Sjkh                bhead->nextbranch = &newbranch;
10149Sjkh		bufautobegin(&branchnum);
10159Sjkh		getbranchno(bhead->hsh->num, &branchnum);
10169Sjkh		incnum(branchnum.string, num);
10179Sjkh		bufautoend(&branchnum);
10189Sjkh		bufscat(num, ".1");
101911894Speter		newbranch.nextbranch = 0;
10209Sjkh        } else {
10219Sjkh                /* place the branch properly */
102211894Speter		field = numlength - ((numlength&1) ^ 1);
10239Sjkh                /* field of branch number */
10249Sjkh		btrail = &branchpoint->branches;
10259Sjkh		while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) {
10269Sjkh			btrail = &(*btrail)->nextbranch;
10279Sjkh			if (!*btrail) {
10289Sjkh				result = -1;
10299Sjkh				break;
10309Sjkh			}
10319Sjkh                }
10329Sjkh		if (result < 0) {
10339Sjkh                        /* insert/append new branchhead */
10349Sjkh			newbranch.nextbranch = *btrail;
10359Sjkh			*btrail = &newbranch;
10369Sjkh			if (numlength&1) bufscat(num, ".1");
10379Sjkh                } else {
10389Sjkh                        /* branch exists; append to end */
10399Sjkh			bufautobegin(&branchnum);
10409Sjkh			getbranchno(num->string, &branchnum);
104111894Speter			targetdelta = genrevs(
104211894Speter				branchnum.string, (char*)0, (char*)0, (char*)0,
104311894Speter				&gendeltas
104411894Speter			);
10459Sjkh			bufautoend(&branchnum);
10469Sjkh			if (!targetdelta)
10479Sjkh			    return -1;
10489Sjkh			if (cmpnum(num->string,targetdelta->num) <= 0) {
104911894Speter				rcserror("revision %s too low; must be higher than %s",
105011894Speter				      num->string, targetdelta->num
105111894Speter				);
10529Sjkh				return -1;
10539Sjkh                        }
105411894Speter			if (!removedlock
105511894Speter			    && 0 <= (removedlock = removelock(targetdelta))
105611894Speter			) {
10579Sjkh			    if (numlength&1)
10589Sjkh				incnum(targetdelta->num,num);
10599Sjkh			    targetdelta->next = &newdelta;
10609Sjkh			    newdelta.next = 0;
10619Sjkh			}
10629Sjkh			return removedlock;
10639Sjkh			/* Don't do anything to newbranch.  */
10649Sjkh                }
10659Sjkh        }
10669Sjkh        newbranch.hsh = &newdelta;
106711894Speter	newdelta.next = 0;
106811894Speter	if (branchpoint->lockedby)
106911894Speter	    if (strcmp(branchpoint->lockedby, getcaller()) == 0)
107011894Speter		return removelock(branchpoint); /* This returns 1.  */
107111894Speter	return removedlock;
10729Sjkh}
10739Sjkh
10749Sjkh	static int
10759Sjkhaddsyms(num)
10769Sjkh	char const *num;
10779Sjkh{
10789Sjkh	register struct Symrev *p;
10799Sjkh
10809Sjkh	for (p = assoclst;  p;  p = p->nextsym)
108111894Speter		if (addsymbol(num, p->ssymbol, p->override)  <  0)
10829Sjkh			return false;
10839Sjkh	return true;
10849Sjkh}
10859Sjkh
10869Sjkh
10879Sjkh	static void
10889Sjkhincnum(onum,nnum)
10899Sjkh	char const *onum;
10909Sjkh	struct buf *nnum;
10919Sjkh/* Increment the last field of revision number onum by one and
10929Sjkh * place the result into nnum.
10939Sjkh */
10949Sjkh{
10959Sjkh	register char *tp, *np;
10969Sjkh	register size_t l;
10979Sjkh
10989Sjkh	l = strlen(onum);
10999Sjkh	bufalloc(nnum, l+2);
11009Sjkh	np = tp = nnum->string;
11019Sjkh	VOID strcpy(np, onum);
11029Sjkh	for (tp = np + l;  np != tp;  )
11039Sjkh		if (isdigit(*--tp)) {
11049Sjkh			if (*tp != '9') {
11059Sjkh				++*tp;
11069Sjkh				return;
11079Sjkh			}
11089Sjkh			*tp = '0';
11099Sjkh		} else {
11109Sjkh			tp++;
11119Sjkh			break;
11129Sjkh		}
11139Sjkh	/* We changed 999 to 000; now change it to 1000.  */
11149Sjkh	*tp = '1';
11159Sjkh	tp = np + l;
11169Sjkh	*tp++ = '0';
11179Sjkh	*tp = 0;
11189Sjkh}
11199Sjkh
11209Sjkh
11219Sjkh
11229Sjkh	static int
11239Sjkhremovelock(delta)
11249Sjkhstruct hshentry * delta;
11259Sjkh/* function: Finds the lock held by caller on delta,
11269Sjkh * removes it, and returns nonzero if successful.
11279Sjkh * Print an error message and return -1 if there is no such lock.
11289Sjkh * An exception is if !StrictLocks, and caller is the owner of
11299Sjkh * the RCS file. If caller does not have a lock in this case,
11309Sjkh * return 0; return 1 if a lock is actually removed.
11319Sjkh */
11329Sjkh{
113311894Speter	register struct rcslock *next, **trail;
11349Sjkh	char const *num;
11359Sjkh
11369Sjkh        num=delta->num;
11379Sjkh	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
11389Sjkh	    if (next->delta == delta)
11399Sjkh		if (strcmp(getcaller(), next->login) == 0) {
11409Sjkh		    /* We found a lock on delta by caller; delete it.  */
11419Sjkh		    *trail = next->nextlock;
11429Sjkh		    delta->lockedby = 0;
11439Sjkh		    return 1;
11449Sjkh		} else {
114511894Speter		    rcserror("revision %s locked by %s", num, next->login);
11469Sjkh		    return -1;
11479Sjkh                }
11489Sjkh	if (!StrictLocks && myself(RCSstat.st_uid))
11499Sjkh	    return 0;
115011894Speter	rcserror("no lock set by %s for revision %s", getcaller(), num);
11519Sjkh	return -1;
11529Sjkh}
11539Sjkh
11549Sjkh
11559Sjkh
11569Sjkh	static char const *
11579Sjkhgetcurdate()
11589Sjkh/* Return a pointer to the current date.  */
11599Sjkh{
11609Sjkh	static char buffer[datesize]; /* date buffer */
11619Sjkh
116211894Speter	if (!buffer[0])
116311894Speter		time2date(now(), buffer);
11649Sjkh        return buffer;
11659Sjkh}
11669Sjkh
11679Sjkh	static int
11689Sjkh#if has_prototypes
116911894Speterfixwork(mode_t newworkmode, time_t mtime)
11709Sjkh  /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
11719Sjkh#else
11729Sjkh  fixwork(newworkmode, mtime)
11739Sjkh	mode_t newworkmode;
117411894Speter	time_t mtime;
11759Sjkh#endif
11769Sjkh{
11779Sjkh	return
11789Sjkh			1 < workstat.st_nlink
117911894Speter		    ||	(newworkmode&S_IWUSR && !myself(workstat.st_uid))
118011894Speter		    ||	setmtime(workname, mtime) != 0
11819Sjkh		?   -1
118211894Speter	    :	workstat.st_mode == newworkmode  ?  0
118311894Speter#if has_fchmod
118411894Speter	    :	fchmod(Ifileno(workptr), newworkmode) == 0  ?  0
118511894Speter#endif
118611894Speter#if bad_chmod_close
118711894Speter	    :	-1
118811894Speter#else
118911894Speter	    :	chmod(workname, newworkmode)
119011894Speter#endif
119111894Speter	;
11929Sjkh}
11939Sjkh
11949Sjkh	static int
119511894Speterxpandfile(unexfile, delta, exname, dolog)
11969Sjkh	RILE *unexfile;
11979Sjkh	struct hshentry const *delta;
119811894Speter	char const **exname;
119911894Speter	int dolog;
12009Sjkh/*
12019Sjkh * Read unexfile and copy it to a
120211894Speter * file, performing keyword substitution with data from delta.
12039Sjkh * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
12049Sjkh * If successful, stores the stream descriptor into *EXFILEP
120511894Speter * and its name into *EXNAME.
12069Sjkh */
12079Sjkh{
120811894Speter	char const *targetname;
12099Sjkh	int e, r;
12109Sjkh
121111894Speter	targetname = makedirtemp(1);
121211894Speter	if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) {
121311894Speter		eerror(targetname);
121411894Speter		workerror("can't build working file");
12159Sjkh		return -1;
12169Sjkh        }
12179Sjkh	r = 0;
121811894Speter	if (MIN_UNEXPAND <= Expand)
12199Sjkh		fastcopy(unexfile,exfile);
12209Sjkh	else {
12219Sjkh		for (;;) {
122211894Speter			e = expandline(
122311894Speter				unexfile, exfile, delta, false, (FILE*)0, dolog
122411894Speter			);
12259Sjkh			if (e < 0)
12269Sjkh				break;
12279Sjkh			r |= e;
12289Sjkh			if (e <= 1)
12299Sjkh				break;
12309Sjkh		}
12319Sjkh	}
123211894Speter	*exname = targetname;
12339Sjkh	return r & 1;
12349Sjkh}
12359Sjkh
12369Sjkh
12379Sjkh
12389Sjkh
12399Sjkh/* --------------------- G E T L O G M S G --------------------------------*/
12409Sjkh
12419Sjkh
12429Sjkh	static struct cbuf
12439Sjkhgetlogmsg()
12449Sjkh/* Obtain and yield a log message.
12459Sjkh * If a log message is given with -m, yield that message.
12469Sjkh * If this is the initial revision, yield a standard log message.
12479Sjkh * Otherwise, reads a character string from the terminal.
12489Sjkh * Stops after reading EOF or a single '.' on a
12499Sjkh * line. getlogmsg prompts the first time it is called for the
12509Sjkh * log message; during all later calls it asks whether the previous
12519Sjkh * log message can be reused.
12529Sjkh */
12539Sjkh{
12549Sjkh	static char const
12559Sjkh		emptych[] = EMPTYLOG,
12569Sjkh		initialch[] = "Initial revision";
12579Sjkh	static struct cbuf const
12589Sjkh		emptylog = { emptych, sizeof(emptych)-sizeof(char) },
12599Sjkh		initiallog = { initialch, sizeof(initialch)-sizeof(char) };
12609Sjkh	static struct buf logbuf;
12619Sjkh	static struct cbuf logmsg;
12629Sjkh
12639Sjkh	register char *tp;
12649Sjkh	register size_t i;
12659Sjkh	char const *caller;
12669Sjkh
12679Sjkh	if (msg.size) return msg;
12689Sjkh
12699Sjkh	if (keepflag) {
12709Sjkh		/* generate std. log message */
12719Sjkh		caller = getcaller();
12729Sjkh		i = sizeof(ciklog)+strlen(caller)+3;
127311894Speter		bufalloc(&logbuf, i + datesize + zonelenmax);
12749Sjkh		tp = logbuf.string;
12759Sjkh		VOID sprintf(tp, "%s%s at ", ciklog, caller);
12769Sjkh		VOID date2str(getcurdate(), tp+i);
12779Sjkh		logmsg.string = tp;
12789Sjkh		logmsg.size = strlen(tp);
12799Sjkh		return logmsg;
12809Sjkh	}
12819Sjkh
12829Sjkh	if (!targetdelta && (
12839Sjkh		cmpnum(newdelnum.string,"1.1")==0 ||
12849Sjkh		cmpnum(newdelnum.string,"1.0")==0
12859Sjkh	))
12869Sjkh		return initiallog;
12879Sjkh
12889Sjkh	if (logmsg.size) {
12899Sjkh                /*previous log available*/
12909Sjkh	    if (yesorno(true, "reuse log message of previous file? [yn](y): "))
12919Sjkh		return logmsg;
12929Sjkh        }
12939Sjkh
12949Sjkh        /* now read string from stdin */
12959Sjkh	logmsg = getsstdin("m", "log message", "", &logbuf);
12969Sjkh
12979Sjkh        /* now check whether the log message is not empty */
12989Sjkh	if (logmsg.size)
12999Sjkh		return logmsg;
13009Sjkh	return emptylog;
13019Sjkh}
13029Sjkh
13039Sjkh/*  Make a linked list of Symbolic names  */
13049Sjkh
13059Sjkh        static void
13069Sjkhaddassoclst(flag, sp)
130711894Speter	int flag;
130811894Speter	char const *sp;
13099Sjkh{
13109Sjkh        struct Symrev *pt;
13118858Srgrimes
13129Sjkh	pt = talloc(struct Symrev);
13139Sjkh	pt->ssymbol = sp;
13149Sjkh	pt->override = flag;
131511894Speter	pt->nextsym = 0;
131611894Speter	*nextassoc = pt;
131711894Speter	nextassoc = &pt->nextsym;
13189Sjkh}
1319