111894Speter/* Change RCS file attributes.  */
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
3011894Speter/*
3111894Speter * Revision 5.21  1995/06/16 06:19:24  eggert
3211894Speter * Update FSF address.
338858Srgrimes *
3411894Speter * Revision 5.20  1995/06/01 16:23:43  eggert
3511894Speter * (main): Warn if no options were given.  Punctuate messages properly.
3611894Speter *
3711894Speter * (sendmail): Rewind mailmess before flushing it.
3811894Speter * Output another warning if mail should work but fails.
3911894Speter *
4011894Speter * (buildeltatext): Pass "--binary" if -kb and if --binary makes a difference.
4111894Speter *
4211894Speter * Revision 5.19  1994/03/17 14:05:48  eggert
4311894Speter * Use ORCSerror to clean up after a fatal error.  Remove lint.
4411894Speter * Specify subprocess input via file descriptor, not file name.  Remove lint.
4511894Speter * Flush stderr after prompt.
4611894Speter *
4711894Speter * Revision 5.18  1993/11/09 17:40:15  eggert
4811894Speter * -V now prints version on stdout and exits.  Don't print usage twice.
4911894Speter *
5011894Speter * Revision 5.17  1993/11/03 17:42:27  eggert
5111894Speter * Add -z.  Don't lose track of -m or -t when there are no other changes.
5211894Speter * Don't discard ignored phrases.  Improve quality of diagnostics.
5311894Speter *
5411894Speter * Revision 5.16  1992/07/28  16:12:44  eggert
5511894Speter * rcs -l now asks whether you want to break the lock.
5611894Speter * Add -V.  Set RCS file's mode and time at right moment.
5711894Speter *
5811894Speter * Revision 5.15  1992/02/17  23:02:20  eggert
5911894Speter * Add -T.
6011894Speter *
6111894Speter * Revision 5.14  1992/01/27  16:42:53  eggert
6211894Speter * Add -M.  Avoid invoking umask(); it's one less thing to configure.
6311894Speter * Add support for bad_creat0.  lint -> RCS_lint
6411894Speter *
6511894Speter * Revision 5.13  1992/01/06  02:42:34  eggert
6611894Speter * Avoid changing RCS file in common cases where no change can occur.
6711894Speter *
689Sjkh * Revision 5.12  1991/11/20  17:58:08  eggert
699Sjkh * Don't read the delta tree from a nonexistent RCS file.
709Sjkh *
719Sjkh * Revision 5.11  1991/10/07  17:32:46  eggert
729Sjkh * Remove lint.
739Sjkh *
749Sjkh * Revision 5.10  1991/08/19  23:17:54  eggert
759Sjkh * Add -m, -r$, piece tables.  Revision separator is `:', not `-'.  Tune.
769Sjkh *
779Sjkh * Revision 5.9  1991/04/21  11:58:18  eggert
789Sjkh * Add -x, RCSINIT, MS-DOS support.
799Sjkh *
809Sjkh * Revision 5.8  1991/02/25  07:12:38  eggert
819Sjkh * strsave -> str_save (DG/UX name clash)
829Sjkh * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability
839Sjkh *
849Sjkh * Revision 5.7  1990/12/18  17:19:21  eggert
859Sjkh * Fix bug with multiple -n and -N options.
869Sjkh *
879Sjkh * Revision 5.6  1990/12/04  05:18:40  eggert
889Sjkh * Use -I for prompts and -q for diagnostics.
899Sjkh *
909Sjkh * Revision 5.5  1990/11/11  00:06:35  eggert
919Sjkh * Fix `rcs -e' core dump.
929Sjkh *
939Sjkh * Revision 5.4  1990/11/01  05:03:33  eggert
949Sjkh * Add -I and new -t behavior.  Permit arbitrary data in logs.
959Sjkh *
969Sjkh * Revision 5.3  1990/10/04  06:30:16  eggert
979Sjkh * Accumulate exit status across files.
989Sjkh *
999Sjkh * Revision 5.2  1990/09/04  08:02:17  eggert
1009Sjkh * Standardize yes-or-no procedure.
1019Sjkh *
1029Sjkh * Revision 5.1  1990/08/29  07:13:51  eggert
1039Sjkh * Remove unused setuid support.  Clean old log messages too.
1049Sjkh *
1059Sjkh * Revision 5.0  1990/08/22  08:12:42  eggert
1069Sjkh * Don't lose names when applying -a option to multiple files.
1079Sjkh * Remove compile-time limits; use malloc instead.  Add setuid support.
1089Sjkh * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
1099Sjkh * Ansify and Posixate.  Add -V.  Fix umask bug.  Make linting easier.  Tune.
1109Sjkh * Yield proper exit status.  Check diff's output.
1119Sjkh *
1129Sjkh * Revision 4.11  89/05/01  15:12:06  narten
1139Sjkh * changed copyright header to reflect current distribution rules
1148858Srgrimes *
1159Sjkh * Revision 4.10  88/11/08  16:01:54  narten
1169Sjkh * didn't install previous patch correctly
1178858Srgrimes *
1189Sjkh * Revision 4.9  88/11/08  13:56:01  narten
1199Sjkh * removed include <sysexits.h> (not needed)
1209Sjkh * minor fix for -A option
1218858Srgrimes *
1229Sjkh * Revision 4.8  88/08/09  19:12:27  eggert
1239Sjkh * Don't access freed storage.
1249Sjkh * Use execv(), not system(); yield proper exit status; remove lint.
1258858Srgrimes *
1269Sjkh * Revision 4.7  87/12/18  11:37:17  narten
1279Sjkh * lint cleanups (Guy Harris)
1288858Srgrimes *
1299Sjkh * Revision 4.6  87/10/18  10:28:48  narten
1308858Srgrimes * Updating verison numbers. Changes relative to 1.1 are actually
1319Sjkh * relative to 4.3
1328858Srgrimes *
1339Sjkh * Revision 1.4  87/09/24  13:58:52  narten
1348858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf
1359Sjkh * warnings)
1368858Srgrimes *
1379Sjkh * Revision 1.3  87/03/27  14:21:55  jenkins
1389Sjkh * Port to suns
1398858Srgrimes *
1409Sjkh * Revision 1.2  85/12/17  13:59:09  albitz
1419Sjkh * Changed setstate to rcs_setstate because of conflict with random.o.
1428858Srgrimes *
1439Sjkh * Revision 4.3  83/12/15  12:27:33  wft
1449Sjkh * rcs -u now breaks most recent lock if it can't find a lock by the caller.
1458858Srgrimes *
1469Sjkh * Revision 4.2  83/12/05  10:18:20  wft
1479Sjkh * Added conditional compilation for sending mail.
1489Sjkh * Alternatives: V4_2BSD, V6, USG, and other.
1498858Srgrimes *
1509Sjkh * Revision 4.1  83/05/10  16:43:02  wft
1519Sjkh * Simplified breaklock(); added calls to findlock() and getcaller().
1529Sjkh * Added option -b (default branch). Updated -s and -w for -b.
1539Sjkh * Removed calls to stat(); now done by pairfilenames().
1549Sjkh * Replaced most catchints() calls with restoreints().
1559Sjkh * Removed check for exit status of delivermail().
1569Sjkh * Directed all interactive output to stderr.
1578858Srgrimes *
1589Sjkh * Revision 3.9.1.1  83/12/02  22:08:51  wft
1599Sjkh * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
1608858Srgrimes *
1619Sjkh * Revision 3.9  83/02/15  15:38:39  wft
1629Sjkh * Added call to fastcopy() to copy remainder of RCS file.
1639Sjkh *
1649Sjkh * Revision 3.8  83/01/18  17:37:51  wft
1659Sjkh * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
1669Sjkh *
1679Sjkh * Revision 3.7  83/01/15  18:04:25  wft
1689Sjkh * Removed putree(); replaced with puttree() in rcssyn.c.
1699Sjkh * Combined putdellog() and scanlogtext(); deleted putdellog().
1709Sjkh * Cleaned up diagnostics and error messages. Fixed problem with
1719Sjkh * mutilated files in case of deletions in 2 files in a single command.
1729Sjkh * Changed marking of selector from 'D' to DELETE.
1739Sjkh *
1749Sjkh * Revision 3.6  83/01/14  15:37:31  wft
1759Sjkh * Added ignoring of interrupts while new RCS file is renamed;
1769Sjkh * Avoids deletion of RCS files by interrupts.
1779Sjkh *
1789Sjkh * Revision 3.5  82/12/10  21:11:39  wft
1799Sjkh * Removed unused variables, fixed checking of return code from diff,
1809Sjkh * introduced variant COMPAT2 for skipping Suffix on -A files.
1819Sjkh *
1829Sjkh * Revision 3.4  82/12/04  13:18:20  wft
1839Sjkh * Replaced getdelta() with gettree(), changed breaklock to update
1849Sjkh * field lockedby, added some diagnostics.
1859Sjkh *
1869Sjkh * Revision 3.3  82/12/03  17:08:04  wft
1879Sjkh * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
1889Sjkh * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
1899Sjkh * fixed -u for missing revno. Disambiguated structure members.
1909Sjkh *
1919Sjkh * Revision 3.2  82/10/18  21:05:07  wft
1929Sjkh * rcs -i now generates a file mode given by the umask minus write permission;
1939Sjkh * otherwise, rcs keeps the mode, but removes write permission.
1949Sjkh * I added a check for write error, fixed call to getlogin(), replaced
1959Sjkh * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
1969Sjkh * conflicting, long identifiers.
1979Sjkh *
1989Sjkh * Revision 3.1  82/10/13  16:11:07  wft
1999Sjkh * fixed type of variables receiving from getc() (char -> int).
2009Sjkh */
2019Sjkh
2029Sjkh
2039Sjkh#include "rcsbase.h"
2049Sjkh
2059Sjkhstruct  Lockrev {
2069Sjkh	char const *revno;
2079Sjkh        struct  Lockrev   * nextrev;
2089Sjkh};
2099Sjkh
2109Sjkhstruct  Symrev {
2119Sjkh	char const *revno;
2129Sjkh	char const *ssymbol;
2139Sjkh        int     override;
2149Sjkh        struct  Symrev  * nextsym;
2159Sjkh};
2169Sjkh
2179Sjkhstruct Message {
2189Sjkh	char const *revno;
2199Sjkh	struct cbuf message;
2209Sjkh	struct Message *nextmessage;
2219Sjkh};
2229Sjkh
2239Sjkhstruct  Status {
2249Sjkh	char const *revno;
2259Sjkh	char const *status;
2269Sjkh        struct  Status  * nextstatus;
2279Sjkh};
2289Sjkh
2299Sjkhenum changeaccess {append, erase};
2309Sjkhstruct chaccess {
2319Sjkh	char const *login;
2329Sjkh	enum changeaccess command;
2339Sjkh	struct chaccess *nextchaccess;
2349Sjkh};
2359Sjkh
2369Sjkhstruct delrevpair {
2379Sjkh	char const *strt;
2389Sjkh	char const *end;
2399Sjkh        int     code;
2409Sjkh};
2419Sjkh
24211894Speterstatic int branchpoint P((struct hshentry*,struct hshentry*));
24311894Speterstatic int breaklock P((struct hshentry const*));
2449Sjkhstatic int buildeltatext P((struct hshentries const*));
24511894Speterstatic int doaccess P((void));
24611894Speterstatic int doassoc P((void));
24711894Speterstatic int dolocks P((void));
24811894Speterstatic int domessages P((void));
24911894Speterstatic int rcs_setstate P((char const*,char const*));
2509Sjkhstatic int removerevs P((void));
2519Sjkhstatic int sendmail P((char const*,char const*));
25211894Speterstatic int setlock P((char const*));
25311894Speterstatic struct Lockrev **rmnewlocklst P((char const*));
25411894Speterstatic struct hshentry *searchcutpt P((char const*,int,struct hshentries*));
2559Sjkhstatic void buildtree P((void));
2569Sjkhstatic void cleanup P((void));
2579Sjkhstatic void getaccessor P((char*,enum changeaccess));
2589Sjkhstatic void getassoclst P((int,char*));
2599Sjkhstatic void getchaccess P((char const*,enum changeaccess));
2609Sjkhstatic void getdelrev P((char*));
2619Sjkhstatic void getmessage P((char*));
2629Sjkhstatic void getstates P((char*));
2639Sjkhstatic void scanlogtext P((struct hshentry*,int));
2649Sjkh
2659Sjkhstatic struct buf numrev;
2669Sjkhstatic char const *headstate;
2679Sjkhstatic int chgheadstate, exitstatus, lockhead, unlockcaller;
26811894Speterstatic int suppress_mail;
2699Sjkhstatic struct Lockrev *newlocklst, *rmvlocklst;
27011894Speterstatic struct Message *messagelst, **nextmessage;
27111894Speterstatic struct Status *statelst, **nextstate;
27211894Speterstatic struct Symrev *assoclst, **nextassoc;
2739Sjkhstatic struct chaccess *chaccess, **nextchaccess;
2749Sjkhstatic struct delrevpair delrev;
2759Sjkhstatic struct hshentry *cuthead, *cuttail, *delstrt;
2769Sjkhstatic struct hshentries *gendeltas;
2779Sjkh
27850472SpetermainProg(rcsId, "rcs", "$FreeBSD$")
2799Sjkh{
2809Sjkh	static char const cmdusage[] =
28111894Speter		"\nrcs usage: rcs -{ae}logins -Afile -{blu}[rev] -cstring -{iILqTU} -ksubst -mrev:msg -{nN}name[:[rev]] -orange -sstate[:rev] -t[text] -Vn -xsuff -zzone file ...";
2829Sjkh
2839Sjkh	char *a, **newargv, *textfile;
2849Sjkh	char const *branchsym, *commsyml;
28511894Speter	int branchflag, changed, expmode, initflag;
28611894Speter	int strictlock, strict_selected, textflag;
28711894Speter	int keepRCStime, Ttimeflag;
28811894Speter	size_t commsymlen;
2899Sjkh	struct buf branchnum;
29011894Speter	struct Lockrev *lockpt;
29111894Speter	struct Lockrev **curlock, **rmvlock;
2929Sjkh        struct  Status  * curstate;
2939Sjkh
2949Sjkh	nosetid();
2959Sjkh
29611894Speter	nextassoc = &assoclst;
2979Sjkh	nextchaccess = &chaccess;
29811894Speter	nextmessage = &messagelst;
29911894Speter	nextstate = &statelst;
30011894Speter	branchsym = commsyml = textfile = 0;
3019Sjkh	branchflag = strictlock = false;
3029Sjkh	bufautobegin(&branchnum);
30311894Speter	commsymlen = 0;
30411894Speter	curlock = &newlocklst;
30511894Speter	rmvlock = &rmvlocklst;
3069Sjkh	expmode = -1;
3079Sjkh	suffixes = X_DEFAULT;
3089Sjkh        initflag= textflag = false;
3099Sjkh        strict_selected = 0;
31011894Speter	Ttimeflag = false;
3119Sjkh
3129Sjkh        /*  preprocessing command options    */
31311894Speter	if (1 < argc  &&  argv[1][0] != '-')
31411894Speter		warn("No options were given; this usage is obsolescent.");
31511894Speter
3169Sjkh	argc = getRCSINIT(argc, argv, &newargv);
3179Sjkh	argv = newargv;
3189Sjkh	while (a = *++argv,  0<--argc && *a++=='-') {
3199Sjkh		switch (*a++) {
3209Sjkh
3219Sjkh		case 'i':   /*  initial version  */
3229Sjkh                        initflag = true;
3239Sjkh                        break;
3249Sjkh
3259Sjkh                case 'b':  /* change default branch */
3269Sjkh			if (branchflag) redefined('b');
3279Sjkh                        branchflag= true;
3289Sjkh			branchsym = a;
3299Sjkh                        break;
3309Sjkh
3319Sjkh                case 'c':   /*  change comment symbol   */
3329Sjkh			if (commsyml) redefined('c');
3339Sjkh			commsyml = a;
33411894Speter			commsymlen = strlen(a);
3359Sjkh                        break;
3369Sjkh
3379Sjkh                case 'a':  /*  add new accessor   */
3389Sjkh			getaccessor(*argv+1, append);
3399Sjkh                        break;
3409Sjkh
3419Sjkh                case 'A':  /*  append access list according to accessfile  */
3429Sjkh			if (!*a) {
34311894Speter			    error("missing pathname after -A");
3449Sjkh                            break;
3459Sjkh                        }
3469Sjkh			*argv = a;
34711894Speter			if (0 < pairnames(1,argv,rcsreadopen,true,false)) {
3489Sjkh			    while (AccessList) {
3499Sjkh				getchaccess(str_save(AccessList->login),append);
3509Sjkh				AccessList = AccessList->nextaccess;
3519Sjkh			    }
3529Sjkh			    Izclose(&finptr);
3539Sjkh                        }
3549Sjkh                        break;
3559Sjkh
3569Sjkh                case 'e':    /*  remove accessors   */
3579Sjkh			getaccessor(*argv+1, erase);
3589Sjkh                        break;
3599Sjkh
3609Sjkh                case 'l':    /*   lock a revision if it is unlocked   */
3619Sjkh			if (!*a) {
3629Sjkh			    /* Lock head or default branch.  */
3639Sjkh                            lockhead = true;
3649Sjkh                            break;
3659Sjkh                        }
36611894Speter			*curlock = lockpt = talloc(struct Lockrev);
3679Sjkh			lockpt->revno = a;
36811894Speter			lockpt->nextrev = 0;
36911894Speter			curlock = &lockpt->nextrev;
3709Sjkh                        break;
3719Sjkh
3729Sjkh                case 'u':   /*  release lock of a locked revision   */
3739Sjkh			if (!*a) {
3749Sjkh                            unlockcaller=true;
3759Sjkh                            break;
3769Sjkh                        }
37711894Speter			*rmvlock = lockpt = talloc(struct Lockrev);
3789Sjkh			lockpt->revno = a;
37911894Speter			lockpt->nextrev = 0;
38011894Speter			rmvlock = &lockpt->nextrev;
38111894Speter			curlock = rmnewlocklst(lockpt->revno);
3829Sjkh                        break;
3839Sjkh
3849Sjkh                case 'L':   /*  set strict locking */
38511894Speter			if (strict_selected) {
3869Sjkh			   if (!strictlock)	  /* Already selected -U? */
38711894Speter			       warn("-U overridden by -L");
3889Sjkh                        }
3899Sjkh                        strictlock = true;
39011894Speter			strict_selected = true;
3919Sjkh                        break;
3929Sjkh
3939Sjkh                case 'U':   /*  release strict locking */
39411894Speter			if (strict_selected) {
3959Sjkh			   if (strictlock)	  /* Already selected -L? */
39611894Speter			       warn("-L overridden by -U");
3979Sjkh                        }
39811894Speter			strict_selected = true;
3999Sjkh                        break;
4009Sjkh
4019Sjkh                case 'n':    /*  add new association: error, if name exists */
4029Sjkh			if (!*a) {
4039Sjkh			    error("missing symbolic name after -n");
4049Sjkh                            break;
4059Sjkh                        }
4069Sjkh                        getassoclst(false, (*argv)+1);
4079Sjkh                        break;
4089Sjkh
4099Sjkh                case 'N':   /*  add or change association   */
4109Sjkh			if (!*a) {
4119Sjkh			    error("missing symbolic name after -N");
4129Sjkh                            break;
4139Sjkh                        }
4149Sjkh                        getassoclst(true, (*argv)+1);
4159Sjkh                        break;
4169Sjkh
4179Sjkh		case 'm':   /*  change log message  */
4189Sjkh			getmessage(a);
4199Sjkh			break;
4209Sjkh
42111894Speter		case 'M':   /*  do not send mail */
42211894Speter			suppress_mail = true;
42311894Speter			break;
42411894Speter
4259Sjkh		case 'o':   /*  delete revisions  */
4269Sjkh			if (delrev.strt) redefined('o');
4279Sjkh			if (!*a) {
4289Sjkh			    error("missing revision range after -o");
4299Sjkh                            break;
4309Sjkh                        }
4319Sjkh                        getdelrev( (*argv)+1 );
4329Sjkh                        break;
4339Sjkh
4349Sjkh                case 's':   /*  change state attribute of a revision  */
4359Sjkh			if (!*a) {
4369Sjkh			    error("state missing after -s");
4379Sjkh                            break;
4389Sjkh                        }
4399Sjkh                        getstates( (*argv)+1);
4409Sjkh                        break;
4419Sjkh
4429Sjkh                case 't':   /*  change descriptive text   */
4439Sjkh                        textflag=true;
4449Sjkh			if (*a) {
4459Sjkh				if (textfile) redefined('t');
4469Sjkh				textfile = a;
4479Sjkh                        }
4489Sjkh                        break;
4499Sjkh
45011894Speter		case 'T':  /*  do not update last-mod time for minor changes */
45111894Speter			if (*a)
45211894Speter				goto unknown;
45311894Speter			Ttimeflag = true;
45411894Speter			break;
45511894Speter
4569Sjkh		case 'I':
4579Sjkh			interactiveflag = true;
4589Sjkh			break;
4599Sjkh
4609Sjkh                case 'q':
4619Sjkh                        quietflag = true;
4629Sjkh                        break;
4639Sjkh
4649Sjkh		case 'x':
4659Sjkh			suffixes = a;
4669Sjkh			break;
4679Sjkh
4689Sjkh		case 'V':
4699Sjkh			setRCSversion(*argv);
4709Sjkh			break;
4719Sjkh
47211894Speter		case 'z':
47311894Speter			zone_set(a);
47411894Speter			break;
47511894Speter
4769Sjkh		case 'k':    /*  set keyword expand mode  */
4779Sjkh			if (0 <= expmode) redefined('k');
4789Sjkh			if (0 <= (expmode = str2expmode(a)))
4799Sjkh			    break;
4809Sjkh			/* fall into */
4819Sjkh                default:
48211894Speter		unknown:
48311894Speter			error("unknown option: %s%s", *argv, cmdusage);
4849Sjkh                };
4859Sjkh        }  /* end processing of options */
4869Sjkh
48711894Speter	/* Now handle all pathnames.  */
48811894Speter	if (nerror) cleanup();
48911894Speter	else if (argc < 1) faterror("no input file%s", cmdusage);
49011894Speter	else for (;  0 < argc;  cleanup(), ++argv, --argc) {
4919Sjkh
4929Sjkh	ffree();
4939Sjkh
4949Sjkh        if ( initflag ) {
49511894Speter	    switch (pairnames(argc, argv, rcswriteopen, false, false)) {
4969Sjkh                case -1: break;        /*  not exist; ok */
4979Sjkh                case  0: continue;     /*  error         */
49811894Speter		case  1: rcserror("already exists");
4999Sjkh                         continue;
5009Sjkh            }
5019Sjkh	}
5029Sjkh        else  {
50311894Speter	    switch (pairnames(argc, argv, rcswriteopen, true, false)) {
5049Sjkh                case -1: continue;    /*  not exist      */
5059Sjkh                case  0: continue;    /*  errors         */
5069Sjkh                case  1: break;       /*  file exists; ok*/
5079Sjkh            }
5089Sjkh	}
5099Sjkh
5109Sjkh
51111894Speter	/*
51211894Speter	 * RCSname contains the name of the RCS file, and
51311894Speter	 * workname contains the name of the working file.
5149Sjkh         * if !initflag, finptr contains the file descriptor for the
5159Sjkh         * RCS file. The admin node is initialized.
5169Sjkh         */
5179Sjkh
51811894Speter	diagnose("RCS file: %s\n", RCSname);
5199Sjkh
52011894Speter	changed = initflag | textflag;
52111894Speter	keepRCStime = Ttimeflag;
52211894Speter	if (!initflag) {
5239Sjkh		if (!checkaccesslist()) continue;
5249Sjkh		gettree(); /* Read the delta tree.  */
5259Sjkh	}
5269Sjkh
5279Sjkh        /*  update admin. node    */
52811894Speter	if (strict_selected) {
52911894Speter		changed  |=  StrictLocks ^ strictlock;
53011894Speter		StrictLocks = strictlock;
53111894Speter	}
53211894Speter	if (
53311894Speter	    commsyml &&
53411894Speter	    (
53511894Speter		commsymlen != Comment.size ||
53611894Speter		memcmp(commsyml, Comment.string, commsymlen) != 0
53711894Speter	    )
53811894Speter	) {
5399Sjkh		Comment.string = commsyml;
5409Sjkh		Comment.size = strlen(commsyml);
54111894Speter		changed = true;
5429Sjkh	}
54311894Speter	if (0 <= expmode  &&  Expand != expmode) {
54411894Speter		Expand = expmode;
54511894Speter		changed = true;
54611894Speter	}
5479Sjkh
5489Sjkh        /* update default branch */
5499Sjkh	if (branchflag && expandsym(branchsym, &branchnum)) {
5509Sjkh	    if (countnumflds(branchnum.string)) {
55111894Speter		if (cmpnum(Dbranch, branchnum.string) != 0) {
55211894Speter			Dbranch = branchnum.string;
55311894Speter			changed = true;
55411894Speter		}
5559Sjkh            } else
55611894Speter		if (Dbranch) {
55711894Speter			Dbranch = 0;
55811894Speter			changed = true;
55911894Speter		}
56011894Speter	}
5619Sjkh
56211894Speter	changed |= doaccess();	/* Update access list.  */
5639Sjkh
56411894Speter	changed |= doassoc();	/* Update association list.  */
5659Sjkh
56611894Speter	changed |= dolocks();	/* Update locks.  */
5679Sjkh
56811894Speter	changed |= domessages();	/* Update log messages.  */
5699Sjkh
5709Sjkh        /*  update state attribution  */
5719Sjkh        if (chgheadstate) {
5729Sjkh            /* change state of default branch or head */
57311894Speter	    if (!Dbranch) {
57411894Speter		if (!Head)
57511894Speter		    rcswarn("can't change states in an empty tree");
57611894Speter		else if (strcmp(Head->state, headstate) != 0) {
57711894Speter		    Head->state = headstate;
57811894Speter		    changed = true;
57911894Speter		}
58011894Speter	    } else
58111894Speter		changed |= rcs_setstate(Dbranch,headstate);
5829Sjkh        }
58311894Speter	for (curstate = statelst;  curstate;  curstate = curstate->nextstatus)
58411894Speter	    changed |= rcs_setstate(curstate->revno,curstate->status);
5859Sjkh
58611894Speter	cuthead = cuttail = 0;
5879Sjkh	if (delrev.strt && removerevs()) {
5889Sjkh            /*  rebuild delta tree if some deltas are deleted   */
5899Sjkh            if ( cuttail )
59011894Speter		VOID genrevs(
59111894Speter			cuttail->num, (char *)0, (char *)0, (char *)0,
59211894Speter			&gendeltas
59311894Speter		);
5949Sjkh            buildtree();
59511894Speter	    changed = true;
59611894Speter	    keepRCStime = false;
5979Sjkh        }
5989Sjkh
5999Sjkh	if (nerror)
6009Sjkh		continue;
6019Sjkh
60211894Speter	putadmin();
6039Sjkh        if ( Head )
6049Sjkh           puttree(Head, frewrite);
6059Sjkh	putdesc(textflag,textfile);
6069Sjkh
6079Sjkh        if ( Head) {
60811894Speter	    if (delrev.strt || messagelst) {
6099Sjkh		if (!cuttail || buildeltatext(gendeltas)) {
6109Sjkh		    advise_access(finptr, MADV_SEQUENTIAL);
61111894Speter		    scanlogtext((struct hshentry *)0, false);
6129Sjkh                    /* copy rest of delta text nodes that are not deleted      */
61311894Speter		    changed = true;
6149Sjkh		}
6159Sjkh            }
6169Sjkh        }
6179Sjkh
61811894Speter	if (initflag) {
61911894Speter		/* Adjust things for donerewrite's sake.  */
62011894Speter		if (stat(workname, &RCSstat) != 0) {
62111894Speter#		    if bad_creat0
62211894Speter			mode_t m = umask(0);
62311894Speter			(void) umask(m);
62411894Speter			RCSstat.st_mode = (S_IRUSR|S_IRGRP|S_IROTH) & ~m;
62511894Speter#		    else
62611894Speter			changed = -1;
62711894Speter#		    endif
62811894Speter		}
62911894Speter		RCSstat.st_nlink = 0;
63011894Speter		keepRCStime = false;
63111894Speter	}
63211894Speter	if (donerewrite(changed,
63311894Speter		keepRCStime ? RCSstat.st_mtime : (time_t)-1
63411894Speter	) != 0)
63511894Speter	    break;
63611894Speter
63711894Speter	diagnose("done\n");
63811894Speter	}
63911894Speter
6409Sjkh	tempunlink();
6419Sjkh	exitmain(exitstatus);
6429Sjkh}       /* end of main (rcs) */
6439Sjkh
6449Sjkh	static void
6459Sjkhcleanup()
6469Sjkh{
6479Sjkh	if (nerror) exitstatus = EXIT_FAILURE;
6489Sjkh	Izclose(&finptr);
6499Sjkh	Ozclose(&fcopy);
65011894Speter	ORCSclose();
6519Sjkh	dirtempunlink();
6529Sjkh}
6539Sjkh
65411894Speter	void
6559Sjkhexiterr()
6569Sjkh{
65711894Speter	ORCSerror();
6589Sjkh	dirtempunlink();
6599Sjkh	tempunlink();
6609Sjkh	_exit(EXIT_FAILURE);
6619Sjkh}
6629Sjkh
6639Sjkh
6649Sjkh	static void
6659Sjkhgetassoclst(flag, sp)
6669Sjkhint     flag;
6679Sjkhchar    * sp;
6689Sjkh/*  Function:   associate a symbolic name to a revision or branch,      */
6699Sjkh/*              and store in assoclst                                   */
6709Sjkh
6719Sjkh{
6729Sjkh        struct   Symrev  * pt;
6739Sjkh	char const *temp;
6749Sjkh        int                c;
6759Sjkh
67611894Speter	while ((c = *++sp) == ' ' || c == '\t' || c =='\n')
67711894Speter	    continue;
6789Sjkh        temp = sp;
67911894Speter	sp = checksym(sp, ':');  /*  check for invalid symbolic name  */
6809Sjkh	c = *sp;   *sp = '\0';
6819Sjkh        while( c == ' ' || c == '\t' || c == '\n')  c = *++sp;
6829Sjkh
6839Sjkh        if ( c != ':' && c != '\0') {
6849Sjkh	    error("invalid string %s after option -n or -N",sp);
6859Sjkh            return;
6869Sjkh        }
6879Sjkh
6889Sjkh	pt = talloc(struct Symrev);
6899Sjkh        pt->ssymbol = temp;
6909Sjkh        pt->override = flag;
6919Sjkh	if (c == '\0')  /*  delete symbol  */
69211894Speter	    pt->revno = 0;
6939Sjkh        else {
69411894Speter	    while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
69511894Speter		continue;
6969Sjkh	    pt->revno = sp;
6979Sjkh        }
69811894Speter	pt->nextsym = 0;
69911894Speter	*nextassoc = pt;
70011894Speter	nextassoc = &pt->nextsym;
7019Sjkh}
7029Sjkh
7039Sjkh
7049Sjkh	static void
7059Sjkhgetchaccess(login, command)
7069Sjkh	char const *login;
7079Sjkh	enum changeaccess command;
7089Sjkh{
7099Sjkh	register struct chaccess *pt;
7109Sjkh
71111894Speter	pt = talloc(struct chaccess);
7129Sjkh	pt->login = login;
7139Sjkh	pt->command = command;
71411894Speter	pt->nextchaccess = 0;
71511894Speter	*nextchaccess = pt;
7169Sjkh	nextchaccess = &pt->nextchaccess;
7179Sjkh}
7189Sjkh
7199Sjkh
7209Sjkh
7219Sjkh	static void
7229Sjkhgetaccessor(opt, command)
7239Sjkh	char *opt;
7249Sjkh	enum changeaccess command;
7259Sjkh/*   Function:  get the accessor list of options -e and -a,     */
7269Sjkh/*		and store in chaccess				*/
7279Sjkh
7289Sjkh
7299Sjkh{
7309Sjkh        register c;
7319Sjkh	register char *sp;
7329Sjkh
7339Sjkh	sp = opt;
73411894Speter	while ((c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',')
73511894Speter	    continue;
7369Sjkh        if ( c == '\0') {
7379Sjkh	    if (command == erase  &&  sp-opt == 1) {
73811894Speter		getchaccess((char*)0, command);
7399Sjkh		return;
7409Sjkh	    }
7419Sjkh	    error("missing login name after option -a or -e");
7429Sjkh	    return;
7439Sjkh        }
7449Sjkh
7459Sjkh        while( c != '\0') {
7469Sjkh		getchaccess(sp, command);
7479Sjkh		sp = checkid(sp,',');
7489Sjkh		c = *sp;   *sp = '\0';
7499Sjkh                while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
7509Sjkh        }
7519Sjkh}
7529Sjkh
7539Sjkh
7549Sjkh	static void
7559Sjkhgetmessage(option)
7569Sjkh	char *option;
7579Sjkh{
7589Sjkh	struct Message *pt;
7599Sjkh	struct cbuf cb;
7609Sjkh	char *m;
7619Sjkh
7629Sjkh	if (!(m = strchr(option, ':'))) {
7639Sjkh		error("-m option lacks revision number");
7649Sjkh		return;
7659Sjkh	}
7669Sjkh	*m++ = 0;
7679Sjkh	cb = cleanlogmsg(m, strlen(m));
7689Sjkh	if (!cb.size) {
7699Sjkh		error("-m option lacks log message");
7709Sjkh		return;
7719Sjkh	}
7729Sjkh	pt = talloc(struct Message);
7739Sjkh	pt->revno = option;
7749Sjkh	pt->message = cb;
7759Sjkh	pt->nextmessage = 0;
77611894Speter	*nextmessage = pt;
77711894Speter	nextmessage = &pt->nextmessage;
7789Sjkh}
7799Sjkh
7809Sjkh
7819Sjkh	static void
7829Sjkhgetstates(sp)
7839Sjkhchar    *sp;
7849Sjkh/*   Function:  get one state attribute and the corresponding   */
7859Sjkh/*              revision and store in statelst                  */
7869Sjkh
7879Sjkh{
7889Sjkh	char const *temp;
7899Sjkh        struct  Status  *pt;
7909Sjkh        register        c;
7919Sjkh
79211894Speter	while ((c = *++sp) ==' ' || c == '\t' || c == '\n')
79311894Speter	    continue;
7949Sjkh        temp = sp;
7959Sjkh	sp = checkid(sp,':');  /* check for invalid state attribute */
7969Sjkh	c = *sp;   *sp = '\0';
7979Sjkh        while( c == ' ' || c == '\t' || c == '\n' )  c = *++sp;
7989Sjkh
7999Sjkh        if ( c == '\0' ) {  /*  change state of def. branch or Head  */
8009Sjkh            chgheadstate = true;
8019Sjkh            headstate  = temp;
8029Sjkh            return;
8039Sjkh        }
8049Sjkh        else if ( c != ':' ) {
8059Sjkh	    error("missing ':' after state in option -s");
8069Sjkh            return;
8079Sjkh        }
8089Sjkh
80911894Speter	while ((c = *++sp) == ' ' || c == '\t' || c == '\n')
81011894Speter	    continue;
8119Sjkh	pt = talloc(struct Status);
8129Sjkh        pt->status     = temp;
8139Sjkh        pt->revno      = sp;
81411894Speter	pt->nextstatus = 0;
81511894Speter	*nextstate = pt;
81611894Speter	nextstate = &pt->nextstatus;
8179Sjkh}
8189Sjkh
8199Sjkh
8209Sjkh
8219Sjkh	static void
8229Sjkhgetdelrev(sp)
8239Sjkhchar    *sp;
8249Sjkh/*   Function:  get revision range or branch to be deleted,     */
8259Sjkh/*              and place in delrev                             */
8269Sjkh{
8279Sjkh        int    c;
8289Sjkh        struct  delrevpair      *pt;
8299Sjkh	int separator;
8309Sjkh
8319Sjkh	pt = &delrev;
83211894Speter	while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
83311894Speter		continue;
8349Sjkh
8359Sjkh	/* Support old ambiguous '-' syntax; this will go away.  */
8369Sjkh	if (strchr(sp,':'))
8379Sjkh		separator = ':';
8389Sjkh	else {
8399Sjkh		if (strchr(sp,'-')  &&  VERSION(5) <= RCSversion)
8409Sjkh		    warn("`-' is obsolete in `-o%s'; use `:' instead", sp);
8419Sjkh		separator = '-';
8429Sjkh	}
8439Sjkh
8449Sjkh	if (c == separator) { /* -o:rev */
84511894Speter	    while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
84611894Speter		continue;
8479Sjkh            pt->strt = sp;    pt->code = 1;
8489Sjkh            while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
8499Sjkh            *sp = '\0';
85011894Speter	    pt->end = 0;
8519Sjkh            return;
8529Sjkh        }
8539Sjkh        else {
8549Sjkh            pt->strt = sp;
8559Sjkh            while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
8569Sjkh		   && c != separator )  c = *++sp;
8579Sjkh            *sp = '\0';
8589Sjkh            while( c == ' ' || c == '\n' || c == '\t' )  c = *++sp;
8599Sjkh            if ( c == '\0' )  {  /*   -o rev or branch   */
86011894Speter		pt->code = 0;
86111894Speter		pt->end = 0;
8629Sjkh                return;
8639Sjkh            }
8649Sjkh	    if (c != separator) {
86511894Speter		error("invalid range %s %s after -o", pt->strt, sp);
8669Sjkh            }
86711894Speter	    while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
86811894Speter		continue;
8699Sjkh	    if (!c) {  /* -orev: */
87011894Speter		pt->code = 2;
87111894Speter		pt->end = 0;
8729Sjkh                return;
8739Sjkh            }
8749Sjkh        }
8759Sjkh	/* -orev1:rev2 */
8769Sjkh	pt->end = sp;  pt->code = 3;
8779Sjkh        while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
8789Sjkh        *sp = '\0';
8799Sjkh}
8809Sjkh
8819Sjkh
8829Sjkh
8839Sjkh
8849Sjkh	static void
8859Sjkhscanlogtext(delta,edit)
8869Sjkh	struct hshentry *delta;
8879Sjkh	int edit;
8889Sjkh/* Function: Scans delta text nodes up to and including the one given
88911894Speter * by delta, or up to last one present, if !delta.
89011894Speter * For the one given by delta (if delta), the log message is saved into
8919Sjkh * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied.
8929Sjkh * Assumes the initial lexeme must be read in first.
89311894Speter * Does not advance nexttok after it is finished, except if !delta.
8949Sjkh */
8959Sjkh{
8969Sjkh	struct hshentry const *nextdelta;
8979Sjkh	struct cbuf cb;
8989Sjkh
8999Sjkh	for (;;) {
9009Sjkh		foutptr = 0;
9019Sjkh		if (eoflex()) {
9029Sjkh                    if(delta)
90311894Speter			rcsfaterror("can't find delta for revision %s",
90411894Speter				delta->num
90511894Speter			);
9069Sjkh		    return; /* no more delta text nodes */
9079Sjkh                }
9089Sjkh		nextlex();
9099Sjkh		if (!(nextdelta=getnum()))
91011894Speter			fatserror("delta number corrupted");
9119Sjkh		if (nextdelta->selector) {
9129Sjkh			foutptr = frewrite;
9139Sjkh			aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
9149Sjkh                }
9159Sjkh		getkeystring(Klog);
9169Sjkh		if (nextdelta == cuttail) {
9179Sjkh			cb = savestring(&curlogbuf);
9189Sjkh			if (!delta->log.string)
9199Sjkh			    delta->log = cleanlogmsg(curlogbuf.string, cb.size);
92011894Speter			nextlex();
92111894Speter			delta->igtext = getphrases(Ktext);
92211894Speter		} else {
92311894Speter			if (nextdelta->log.string && nextdelta->selector) {
92411894Speter				foutptr = 0;
92511894Speter				readstring();
92611894Speter				foutptr = frewrite;
92711894Speter				putstring(foutptr, false, nextdelta->log, true);
92811894Speter				afputc(nextc, foutptr);
92911894Speter			} else
93011894Speter				readstring();
93111894Speter			ignorephrases(Ktext);
93211894Speter		}
9339Sjkh		getkeystring(Ktext);
9349Sjkh
9359Sjkh		if (delta==nextdelta)
9369Sjkh			break;
9379Sjkh		readstring(); /* skip over it */
9389Sjkh
9399Sjkh	}
9409Sjkh	/* got the one we're looking for */
9419Sjkh	if (edit)
94211894Speter		editstring((struct hshentry*)0);
9439Sjkh	else
9449Sjkh		enterstring();
9459Sjkh}
9469Sjkh
9479Sjkh
9489Sjkh
94911894Speter	static struct Lockrev **
9509Sjkhrmnewlocklst(which)
95111894Speter	char const *which;
95211894Speter/* Remove lock to revision WHICH from newlocklst.  */
9539Sjkh{
95411894Speter	struct Lockrev *pt, **pre;
9559Sjkh
95611894Speter	pre = &newlocklst;
95711894Speter	while ((pt = *pre))
95811894Speter	    if (strcmp(pt->revno, which) != 0)
95911894Speter		pre = &pt->nextrev;
96011894Speter	    else {
96111894Speter		*pre = pt->nextrev;
9629Sjkh		tfree(pt);
96311894Speter	    }
9649Sjkh        return pre;
9659Sjkh}
9669Sjkh
9679Sjkh
9689Sjkh
96911894Speter	static int
9709Sjkhdoaccess()
9719Sjkh{
9729Sjkh	register struct chaccess *ch;
9739Sjkh	register struct access **p, *t;
97411894Speter	register int changed = false;
9759Sjkh
9769Sjkh	for (ch = chaccess;  ch;  ch = ch->nextchaccess) {
9779Sjkh		switch (ch->command) {
9789Sjkh		case erase:
97911894Speter			if (!ch->login) {
98011894Speter			    if (AccessList) {
98111894Speter				AccessList = 0;
98211894Speter				changed = true;
98311894Speter			    }
98411894Speter			} else
98511894Speter			    for (p = &AccessList; (t = *p); p = &t->nextaccess)
98611894Speter				if (strcmp(ch->login, t->login) == 0) {
9879Sjkh					*p = t->nextaccess;
98811894Speter					changed = true;
98911894Speter					break;
99011894Speter				}
9919Sjkh			break;
9929Sjkh		case append:
9939Sjkh			for (p = &AccessList;  ;  p = &t->nextaccess)
9949Sjkh				if (!(t = *p)) {
9959Sjkh					*p = t = ftalloc(struct access);
9969Sjkh					t->login = ch->login;
99711894Speter					t->nextaccess = 0;
99811894Speter					changed = true;
9999Sjkh					break;
10009Sjkh				} else if (strcmp(ch->login, t->login) == 0)
10019Sjkh					break;
10029Sjkh			break;
10039Sjkh		}
10049Sjkh	}
100511894Speter	return changed;
10069Sjkh}
10079Sjkh
10089Sjkh
10099Sjkh	static int
10109Sjkhsendmail(Delta, who)
10119Sjkh	char const *Delta, *who;
10129Sjkh/*   Function:  mail to who, informing him that his lock on delta was
10139Sjkh *   broken by caller. Ask first whether to go ahead. Return false on
10149Sjkh *   error or if user decides not to break the lock.
10159Sjkh */
10169Sjkh{
10179Sjkh#ifdef SENDMAIL
10189Sjkh	char const *messagefile;
101911894Speter	int old1, old2, c, status;
10209Sjkh        FILE    * mailmess;
10219Sjkh#endif
10229Sjkh
10239Sjkh
10249Sjkh	aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
102511894Speter	if (suppress_mail)
102611894Speter		return true;
10279Sjkh	if (!yesorno(false, "Do you want to break the lock? [ny](n): "))
10289Sjkh		return false;
10299Sjkh
10309Sjkh        /* go ahead with breaking  */
10319Sjkh#ifdef SENDMAIL
10329Sjkh	messagefile = maketemp(0);
103311894Speter	if (!(mailmess = fopenSafer(messagefile, "w+"))) {
10349Sjkh	    efaterror(messagefile);
10359Sjkh        }
10369Sjkh
10379Sjkh	aprintf(mailmess, "Subject: Broken lock on %s\n\nYour lock on revision %s of file %s\nhas been broken by %s for the following reason:\n",
103811894Speter		basefilename(RCSname), Delta, getfullRCSname(), getcaller()
10399Sjkh	);
10409Sjkh	aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr);
104111894Speter	eflush();
10429Sjkh
10439Sjkh        old1 = '\n';    old2 = ' ';
10449Sjkh        for (; ;) {
10459Sjkh	    c = getcstdin();
10469Sjkh	    if (feof(stdin)) {
10479Sjkh		aprintf(mailmess, "%c\n", old1);
10489Sjkh                break;
10499Sjkh            }
10509Sjkh            else if ( c == '\n' && old1 == '.' && old2 == '\n')
10519Sjkh                break;
10529Sjkh            else {
10539Sjkh		afputc(old1, mailmess);
10549Sjkh                old2 = old1;   old1 = c;
105511894Speter		if (c == '\n') {
105611894Speter		    aputs(">> ", stderr);
105711894Speter		    eflush();
105811894Speter		}
10599Sjkh            }
10609Sjkh        }
106111894Speter	Orewind(mailmess);
106211894Speter	aflush(mailmess);
106311894Speter	status = run(fileno(mailmess), (char*)0, SENDMAIL, who, (char*)0);
10649Sjkh	Ozclose(&mailmess);
106511894Speter	if (status == 0)
106611894Speter		return true;
106711894Speter	warn("Mail failed.");
10689Sjkh#endif
106911894Speter	warn("Mail notification of broken locks is not available.");
107011894Speter	warn("Please tell `%s' why you broke the lock.", who);
10719Sjkh	return(true);
10729Sjkh}
10739Sjkh
10749Sjkh
10759Sjkh
107611894Speter	static int
10779Sjkhbreaklock(delta)
10789Sjkh	struct hshentry const *delta;
10799Sjkh/* function: Finds the lock held by caller on delta,
10809Sjkh * and removes it.
10819Sjkh * Sends mail if a lock different from the caller's is broken.
10829Sjkh * Prints an error message if there is no such lock or error.
10839Sjkh */
10849Sjkh{
108511894Speter	register struct rcslock *next, **trail;
10869Sjkh	char const *num;
10879Sjkh
10889Sjkh	num=delta->num;
108911894Speter	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
10909Sjkh		if (strcmp(num, next->delta->num) == 0) {
10919Sjkh			if (
10929Sjkh				strcmp(getcaller(),next->login) != 0
10939Sjkh			    &&	!sendmail(num, next->login)
10949Sjkh			) {
109511894Speter			    rcserror("revision %s still locked by %s",
109611894Speter				num, next->login
109711894Speter			    );
109811894Speter			    return false;
10999Sjkh			}
110011894Speter			diagnose("%s unlocked\n", next->delta->num);
110111894Speter			*trail = next->nextlock;
110211894Speter			next->delta->lockedby = 0;
110311894Speter			return true;
11049Sjkh                }
110511894Speter	rcserror("no lock set on revision %s", num);
110611894Speter	return false;
11079Sjkh}
11089Sjkh
11099Sjkh
11109Sjkh
11119Sjkh	static struct hshentry *
11129Sjkhsearchcutpt(object, length, store)
11139Sjkh	char const *object;
111411894Speter	int length;
11159Sjkh	struct hshentries *store;
11169Sjkh/*   Function:  Search store and return entry with number being object. */
111711894Speter/*		cuttail = 0, if the entry is Head; otherwise, cuttail   */
11189Sjkh/*              is the entry point to the one with number being object  */
11199Sjkh
11209Sjkh{
112111894Speter	cuthead = 0;
11229Sjkh	while (compartial(store->first->num, object, length)) {
11239Sjkh		cuthead = store->first;
11249Sjkh		store = store->rest;
11259Sjkh	}
11269Sjkh	return store->first;
11279Sjkh}
11289Sjkh
11299Sjkh
11309Sjkh
11319Sjkh	static int
11329Sjkhbranchpoint(strt, tail)
11339Sjkhstruct  hshentry        *strt,  *tail;
11349Sjkh/*   Function: check whether the deltas between strt and tail	*/
11359Sjkh/*		are locked or branch point, return 1 if any is  */
11369Sjkh/*		locked or branch point; otherwise, return 0 and */
11379Sjkh/*		mark deleted					*/
11389Sjkh
11399Sjkh{
11409Sjkh        struct  hshentry    *pt;
114111894Speter	struct rcslock const *lockpt;
11429Sjkh
114311894Speter	for (pt = strt;  pt != tail;  pt = pt->next) {
11449Sjkh            if ( pt->branches ){ /*  a branch point  */
114511894Speter		rcserror("can't remove branch point %s", pt->num);
114611894Speter		return true;
11479Sjkh            }
114811894Speter	    for (lockpt = Locks;  lockpt;  lockpt = lockpt->nextlock)
114911894Speter		if (lockpt->delta == pt) {
115011894Speter		    rcserror("can't remove locked revision %s", pt->num);
115111894Speter		    return true;
115211894Speter		}
115311894Speter	    pt->selector = false;
115411894Speter	    diagnose("deleting revision %s\n",pt->num);
11559Sjkh        }
115611894Speter	return false;
11579Sjkh}
11589Sjkh
11599Sjkh
11609Sjkh
11619Sjkh	static int
11629Sjkhremoverevs()
11639Sjkh/*   Function:  get the revision range to be removed, and place the     */
11649Sjkh/*              first revision removed in delstrt, the revision before  */
116511894Speter/*		delstrt in cuthead (0, if delstrt is head), and the	*/
116611894Speter/*		revision after the last removed revision in cuttail (0	*/
11679Sjkh/*              if the last is a leaf                                   */
11689Sjkh
11699Sjkh{
11709Sjkh	struct  hshentry *target, *target2, *temp;
117111894Speter	int length;
117211894Speter	int cmp;
11739Sjkh
11749Sjkh	if (!expandsym(delrev.strt, &numrev)) return 0;
117511894Speter	target = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
11769Sjkh        if ( ! target ) return 0;
117711894Speter	cmp = cmpnum(target->num, numrev.string);
11789Sjkh	length = countnumflds(numrev.string);
11799Sjkh
11809Sjkh	if (delrev.code == 0) {  /*  -o  rev    or    -o  branch   */
11819Sjkh	    if (length & 1)
11829Sjkh		temp=searchcutpt(target->num,length+1,gendeltas);
118311894Speter	    else if (cmp) {
118411894Speter		rcserror("Revision %s doesn't exist.", numrev.string);
11859Sjkh		return 0;
11869Sjkh	    }
11879Sjkh	    else
11889Sjkh		temp = searchcutpt(numrev.string, length, gendeltas);
11899Sjkh	    cuttail = target->next;
11909Sjkh            if ( branchpoint(temp, cuttail) ) {
119111894Speter		cuttail = 0;
11929Sjkh                return 0;
11939Sjkh            }
11949Sjkh            delstrt = temp;     /* first revision to be removed   */
11959Sjkh            return 1;
11969Sjkh        }
11979Sjkh
11989Sjkh	if (length & 1) {   /*  invalid branch after -o   */
119911894Speter	    rcserror("invalid branch range %s after -o", numrev.string);
12009Sjkh            return 0;
12019Sjkh        }
12029Sjkh
12039Sjkh	if (delrev.code == 1) {  /*  -o  -rev   */
12049Sjkh            if ( length > 2 ) {
12059Sjkh                temp = searchcutpt( target->num, length-1, gendeltas);
12069Sjkh                cuttail = target->next;
12079Sjkh            }
12089Sjkh            else {
12099Sjkh                temp = searchcutpt(target->num, length, gendeltas);
12109Sjkh                cuttail = target;
12119Sjkh                while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
12129Sjkh                    cuttail = cuttail->next;
12139Sjkh            }
12149Sjkh            if ( branchpoint(temp, cuttail) ){
121511894Speter		cuttail = 0;
12169Sjkh                return 0;
12179Sjkh            }
12189Sjkh            delstrt = temp;
12199Sjkh            return 1;
12209Sjkh        }
12219Sjkh
12229Sjkh	if (delrev.code == 2) {   /*  -o  rev-   */
12239Sjkh            if ( length == 2 ) {
12249Sjkh                temp = searchcutpt(target->num, 1,gendeltas);
122511894Speter		if (cmp)
12269Sjkh                    cuttail = target;
12279Sjkh                else
12289Sjkh                    cuttail = target->next;
12299Sjkh            }
12309Sjkh            else  {
123111894Speter		if (cmp) {
12329Sjkh                    cuthead = target;
12339Sjkh                    if ( !(temp = target->next) ) return 0;
12349Sjkh                }
12359Sjkh                else
12369Sjkh                    temp = searchcutpt(target->num, length, gendeltas);
12379Sjkh		getbranchno(temp->num, &numrev);  /* get branch number */
123811894Speter		VOID genrevs(numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas);
12399Sjkh            }
12409Sjkh            if ( branchpoint( temp, cuttail ) ) {
124111894Speter		cuttail = 0;
12429Sjkh                return 0;
12439Sjkh            }
12449Sjkh            delstrt = temp;
12459Sjkh            return 1;
12469Sjkh        }
12479Sjkh
12489Sjkh        /*   -o   rev1-rev2   */
12499Sjkh	if (!expandsym(delrev.end, &numrev)) return 0;
12509Sjkh	if (
12519Sjkh		length != countnumflds(numrev.string)
125211894Speter	    ||	(length>2 && compartial(numrev.string, target->num, length-1))
12539Sjkh	) {
125411894Speter	    rcserror("invalid revision range %s-%s",
125511894Speter		target->num, numrev.string
125611894Speter	    );
12579Sjkh            return 0;
12589Sjkh        }
12599Sjkh
126011894Speter	target2 = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
12619Sjkh        if ( ! target2 ) return 0;
12629Sjkh
12639Sjkh        if ( length > 2) {  /* delete revisions on branches  */
12649Sjkh            if ( cmpnum(target->num, target2->num) > 0) {
126511894Speter		cmp = cmpnum(target2->num, numrev.string);
12669Sjkh                temp = target;
12679Sjkh                target = target2;
12689Sjkh                target2 = temp;
12699Sjkh            }
127011894Speter	    if (cmp) {
12719Sjkh                if ( ! cmpnum(target->num, target2->num) ) {
127211894Speter		    rcserror("Revisions %s-%s don't exist.",
127311894Speter			delrev.strt, delrev.end
127411894Speter		    );
12759Sjkh                    return 0;
12769Sjkh                }
12779Sjkh                cuthead = target;
12789Sjkh                temp = target->next;
12799Sjkh            }
12809Sjkh            else
12819Sjkh                temp = searchcutpt(target->num, length, gendeltas);
12829Sjkh            cuttail = target2->next;
12839Sjkh        }
12849Sjkh        else { /*  delete revisions on trunk  */
12859Sjkh            if ( cmpnum( target->num, target2->num) < 0 ) {
12869Sjkh                temp = target;
12879Sjkh                target = target2;
12889Sjkh                target2 = temp;
12899Sjkh            }
12909Sjkh            else
129111894Speter		cmp = cmpnum(target2->num, numrev.string);
129211894Speter	    if (cmp) {
12939Sjkh                if ( ! cmpnum(target->num, target2->num) ) {
129411894Speter		    rcserror("Revisions %s-%s don't exist.",
129511894Speter			delrev.strt, delrev.end
129611894Speter		    );
12979Sjkh                    return 0;
12989Sjkh                }
12999Sjkh                cuttail = target2;
13009Sjkh            }
13019Sjkh            else
13029Sjkh                cuttail = target2->next;
13039Sjkh            temp = searchcutpt(target->num, length, gendeltas);
13049Sjkh        }
13059Sjkh        if ( branchpoint(temp, cuttail) )  {
130611894Speter	    cuttail = 0;
13079Sjkh            return 0;
13089Sjkh        }
13099Sjkh        delstrt = temp;
13109Sjkh        return 1;
13119Sjkh}
13129Sjkh
13139Sjkh
13149Sjkh
131511894Speter	static int
13169Sjkhdoassoc()
131711894Speter/* Add or delete (if !revno) association that is stored in assoclst.  */
13189Sjkh{
13199Sjkh	char const *p;
132011894Speter	int changed = false;
13219Sjkh	struct Symrev const *curassoc;
132211894Speter	struct assoc **pre, *pt;
13239Sjkh
13249Sjkh        /*  add new associations   */
132511894Speter	for (curassoc = assoclst;  curassoc;  curassoc = curassoc->nextsym) {
132611894Speter	    char const *ssymbol = curassoc->ssymbol;
132711894Speter
132811894Speter	    if (!curassoc->revno) {  /* delete symbol  */
132911894Speter		for (pre = &Symbols;  ;  pre = &pt->nextassoc)
133011894Speter		    if (!(pt = *pre)) {
133111894Speter			rcswarn("can't delete nonexisting symbol %s", ssymbol);
133211894Speter			break;
133311894Speter		    } else if (strcmp(pt->symbol, ssymbol) == 0) {
133411894Speter			*pre = pt->nextassoc;
133511894Speter			changed = true;
133611894Speter			break;
133711894Speter		    }
13389Sjkh	    }
13399Sjkh	    else {
13409Sjkh		if (curassoc->revno[0]) {
13419Sjkh		    p = 0;
13429Sjkh		    if (expandsym(curassoc->revno, &numrev))
13439Sjkh			p = fstr_save(numrev.string);
13449Sjkh		} else if (!(p = tiprev()))
134511894Speter		    rcserror("no latest revision to associate with symbol %s",
134611894Speter			    ssymbol
13479Sjkh		    );
13489Sjkh		if (p)
134911894Speter		    changed |= addsymbol(p, ssymbol, curassoc->override);
13509Sjkh	    }
13519Sjkh        }
135211894Speter	return changed;
13539Sjkh}
13549Sjkh
13559Sjkh
13569Sjkh
135711894Speter	static int
13589Sjkhdolocks()
13599Sjkh/* Function: remove lock for caller or first lock if unlockcaller is set;
13609Sjkh *           remove locks which are stored in rmvlocklst,
13619Sjkh *           add new locks which are stored in newlocklst,
13629Sjkh *           add lock for Dbranch or Head if lockhead is set.
13639Sjkh */
13649Sjkh{
13659Sjkh	struct Lockrev const *lockpt;
13669Sjkh	struct hshentry *target;
136711894Speter	int changed = false;
13689Sjkh
13699Sjkh	if (unlockcaller) { /*  find lock for caller  */
13709Sjkh            if ( Head ) {
13719Sjkh		if (Locks) {
13729Sjkh		    switch (findlock(true, &target)) {
13739Sjkh		      case 0:
137411894Speter			/* remove most recent lock */
137511894Speter			changed |= breaklock(Locks->delta);
13769Sjkh			break;
13779Sjkh		      case 1:
13789Sjkh			diagnose("%s unlocked\n",target->num);
137911894Speter			changed = true;
13809Sjkh			break;
13819Sjkh		    }
13829Sjkh		} else {
138311894Speter		    rcswarn("No locks are set.");
13849Sjkh		}
13859Sjkh            } else {
138611894Speter		rcswarn("can't unlock an empty tree");
13879Sjkh            }
13889Sjkh        }
13899Sjkh
13909Sjkh        /*  remove locks which are stored in rmvlocklst   */
139111894Speter	for (lockpt = rmvlocklst;  lockpt;  lockpt = lockpt->nextrev)
13929Sjkh	    if (expandsym(lockpt->revno, &numrev)) {
139311894Speter		target = genrevs(numrev.string, (char *)0, (char *)0, (char *)0, &gendeltas);
13949Sjkh                if ( target )
13959Sjkh		   if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
139611894Speter			rcserror("can't unlock nonexisting revision %s",
139711894Speter				lockpt->revno
139811894Speter			);
13999Sjkh                   else
140011894Speter			changed |= breaklock(target);
14019Sjkh                        /* breaklock does its own diagnose */
14029Sjkh            }
14039Sjkh
14049Sjkh        /*  add new locks which stored in newlocklst  */
140511894Speter	for (lockpt = newlocklst;  lockpt;  lockpt = lockpt->nextrev)
140611894Speter	    changed |= setlock(lockpt->revno);
14079Sjkh
140811894Speter	if (lockhead) /*  lock default branch or head  */
140911894Speter	    if (Dbranch)
141011894Speter		changed |= setlock(Dbranch);
141111894Speter	    else if (Head)
141211894Speter		changed |= setlock(Head->num);
141311894Speter	    else
141411894Speter		rcswarn("can't lock an empty tree");
141511894Speter	return changed;
14169Sjkh}
14179Sjkh
14189Sjkh
14199Sjkh
142011894Speter	static int
14219Sjkhsetlock(rev)
14229Sjkh	char const *rev;
14239Sjkh/* Function: Given a revision or branch number, finds the corresponding
14249Sjkh * delta and locks it for caller.
14259Sjkh */
14269Sjkh{
14279Sjkh        struct  hshentry *target;
142811894Speter	int r;
14299Sjkh
14309Sjkh	if (expandsym(rev, &numrev)) {
143111894Speter	    target = genrevs(numrev.string, (char*)0, (char*)0,
143211894Speter			     (char*)0, &gendeltas);
14339Sjkh            if ( target )
14349Sjkh	       if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
143511894Speter		    rcserror("can't lock nonexisting revision %s",
143611894Speter			numrev.string
143711894Speter		    );
143811894Speter	       else {
143911894Speter		    if ((r = addlock(target, false)) < 0  &&  breaklock(target))
144011894Speter			r = addlock(target, true);
144111894Speter		    if (0 <= r) {
144211894Speter			if (r)
144311894Speter			    diagnose("%s locked\n", target->num);
144411894Speter			return r;
144511894Speter		    }
144611894Speter	       }
144711894Speter	}
144811894Speter	return 0;
14499Sjkh}
14509Sjkh
14519Sjkh
145211894Speter	static int
14539Sjkhdomessages()
14549Sjkh{
14559Sjkh	struct hshentry *target;
14569Sjkh	struct Message *p;
145711894Speter	int changed = false;
14589Sjkh
14599Sjkh	for (p = messagelst;  p;  p = p->nextmessage)
14609Sjkh	    if (
14619Sjkh		expandsym(p->revno, &numrev)  &&
14629Sjkh		(target = genrevs(
14639Sjkh			numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas
14649Sjkh		))
146511894Speter	    ) {
146611894Speter		/*
146711894Speter		 * We can't check the old log -- it's much later in the file.
146811894Speter		 * We pessimistically assume that it changed.
146911894Speter		 */
14709Sjkh		target->log = p->message;
147111894Speter		changed = true;
147211894Speter	    }
147311894Speter	return changed;
14749Sjkh}
14759Sjkh
14769Sjkh
147711894Speter	static int
14789Sjkhrcs_setstate(rev,status)
14799Sjkh	char const *rev, *status;
14809Sjkh/* Function: Given a revision or branch number, finds the corresponding delta
14819Sjkh * and sets its state to status.
14829Sjkh */
14839Sjkh{
14849Sjkh        struct  hshentry *target;
14859Sjkh
14869Sjkh	if (expandsym(rev, &numrev)) {
148711894Speter	    target = genrevs(numrev.string, (char*)0, (char*)0,
148811894Speter			     (char*)0, &gendeltas);
14899Sjkh            if ( target )
14909Sjkh	       if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
149111894Speter		    rcserror("can't set state of nonexisting revision %s",
149211894Speter			numrev.string
149311894Speter		    );
149411894Speter	       else if (strcmp(target->state, status) != 0) {
14959Sjkh                    target->state = status;
149611894Speter		    return true;
149711894Speter	       }
149811894Speter	}
149911894Speter	return false;
15009Sjkh}
15019Sjkh
15029Sjkh
15039Sjkh
15049Sjkh
15059Sjkh
15069Sjkh	static int
15079Sjkhbuildeltatext(deltas)
15089Sjkh	struct hshentries const *deltas;
15099Sjkh/*   Function:  put the delta text on frewrite and make necessary   */
15109Sjkh/*              change to delta text                                */
15119Sjkh{
15129Sjkh	register FILE *fcut;	/* temporary file to rebuild delta tree */
151311894Speter	char const *cutname;
15149Sjkh
151511894Speter	fcut = 0;
15169Sjkh	cuttail->selector = false;
15179Sjkh	scanlogtext(deltas->first, false);
15189Sjkh        if ( cuthead )  {
151911894Speter	    cutname = maketemp(3);
152011894Speter	    if (!(fcut = fopenSafer(cutname, FOPEN_WPLUS_WORK))) {
152111894Speter		efaterror(cutname);
15229Sjkh            }
15239Sjkh
15249Sjkh	    while (deltas->first != cuthead) {
15259Sjkh		deltas = deltas->rest;
15269Sjkh		scanlogtext(deltas->first, true);
15279Sjkh            }
15289Sjkh
15299Sjkh	    snapshotedit(fcut);
153011894Speter	    Orewind(fcut);
153111894Speter	    aflush(fcut);
15329Sjkh        }
15339Sjkh
15349Sjkh	while (deltas->first != cuttail)
15359Sjkh	    scanlogtext((deltas = deltas->rest)->first, true);
153611894Speter	finishedit((struct hshentry*)0, (FILE*)0, true);
15379Sjkh	Ozclose(&fcopy);
15389Sjkh
153911894Speter	if (fcut) {
154011894Speter	    char const *diffname = maketemp(0);
154111894Speter	    char const *diffv[6 + !!OPEN_O_BINARY];
154211894Speter	    char const **diffp = diffv;
154311894Speter	    *++diffp = DIFF;
154411894Speter	    *++diffp = DIFFFLAGS;
154511894Speter#	    if OPEN_O_BINARY
154611894Speter		if (Expand == BINARY_EXPAND)
154711894Speter		    *++diffp == "--binary";
154811894Speter#	    endif
154911894Speter	    *++diffp = "-";
155011894Speter	    *++diffp = resultname;
155111894Speter	    *++diffp = 0;
155211894Speter	    switch (runv(fileno(fcut), diffname, diffv)) {
15539Sjkh		case DIFF_FAILURE: case DIFF_SUCCESS: break;
155411894Speter		default: rcsfaterror("diff failed");
15559Sjkh	    }
155611894Speter	    Ofclose(fcut);
155711894Speter	    return putdtext(cuttail,diffname,frewrite,true);
15589Sjkh	} else
155911894Speter	    return putdtext(cuttail,resultname,frewrite,false);
15609Sjkh}
15619Sjkh
15629Sjkh
15639Sjkh
15649Sjkh	static void
15659Sjkhbuildtree()
15669Sjkh/*   Function:  actually removes revisions whose selector field  */
15679Sjkh/*		is false, and rebuilds the linkage of deltas.	 */
15689Sjkh/*              asks for reconfirmation if deleting last revision*/
15699Sjkh{
15709Sjkh	struct	hshentry   * Delta;
15719Sjkh        struct  branchhead      *pt, *pre;
15729Sjkh
15739Sjkh        if ( cuthead )
15749Sjkh           if ( cuthead->next == delstrt )
15759Sjkh                cuthead->next = cuttail;
15769Sjkh           else {
15779Sjkh                pre = pt = cuthead->branches;
15789Sjkh                while( pt && pt->hsh != delstrt )  {
15799Sjkh                    pre = pt;
15809Sjkh                    pt = pt->nextbranch;
15819Sjkh                }
15829Sjkh                if ( cuttail )
15839Sjkh                    pt->hsh = cuttail;
15849Sjkh                else if ( pt == pre )
15859Sjkh                    cuthead->branches = pt->nextbranch;
15869Sjkh                else
15879Sjkh                    pre->nextbranch = pt->nextbranch;
15889Sjkh            }
15899Sjkh	else {
159011894Speter	    if (!cuttail && !quietflag) {
15919Sjkh		if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) {
159211894Speter		    rcserror("No revision deleted");
15939Sjkh		    Delta = delstrt;
15949Sjkh		    while( Delta) {
15959Sjkh			Delta->selector = true;
15969Sjkh			Delta = Delta->next;
15979Sjkh		    }
15989Sjkh		    return;
15999Sjkh		}
16009Sjkh	    }
16019Sjkh            Head = cuttail;
16029Sjkh	}
16039Sjkh        return;
16049Sjkh}
16059Sjkh
160611894Speter#if RCS_lint
16079Sjkh/* This lets us lint everything all at once. */
16089Sjkh
16099Sjkhchar const cmdid[] = "";
16109Sjkh
16119Sjkh#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();}
16129Sjkh
16139Sjkh	int
16149Sjkhmain(argc, argv)
16159Sjkh	int argc;
16169Sjkh	char **argv;
16179Sjkh{
16189Sjkh	go(ciId,	ciExit);
16199Sjkh	go(coId,	coExit);
16209Sjkh	go(identId,	identExit);
16219Sjkh	go(mergeId,	mergeExit);
16229Sjkh	go(rcsId,	exiterr);
16239Sjkh	go(rcscleanId,	rcscleanExit);
16249Sjkh	go(rcsdiffId,	rdiffExit);
16259Sjkh	go(rcsmergeId,	rmergeExit);
16269Sjkh	go(rlogId,	rlogExit);
16279Sjkh	return 0;
16289Sjkh}
16299Sjkh#endif
1630