rcs.c revision 11894
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 * $Log: rcs.c,v $
3211894Speter * Revision 5.21  1995/06/16 06:19:24  eggert
3311894Speter * Update FSF address.
348858Srgrimes *
3511894Speter * Revision 5.20  1995/06/01 16:23:43  eggert
3611894Speter * (main): Warn if no options were given.  Punctuate messages properly.
3711894Speter *
3811894Speter * (sendmail): Rewind mailmess before flushing it.
3911894Speter * Output another warning if mail should work but fails.
4011894Speter *
4111894Speter * (buildeltatext): Pass "--binary" if -kb and if --binary makes a difference.
4211894Speter *
4311894Speter * Revision 5.19  1994/03/17 14:05:48  eggert
4411894Speter * Use ORCSerror to clean up after a fatal error.  Remove lint.
4511894Speter * Specify subprocess input via file descriptor, not file name.  Remove lint.
4611894Speter * Flush stderr after prompt.
4711894Speter *
4811894Speter * Revision 5.18  1993/11/09 17:40:15  eggert
4911894Speter * -V now prints version on stdout and exits.  Don't print usage twice.
5011894Speter *
5111894Speter * Revision 5.17  1993/11/03 17:42:27  eggert
5211894Speter * Add -z.  Don't lose track of -m or -t when there are no other changes.
5311894Speter * Don't discard ignored phrases.  Improve quality of diagnostics.
5411894Speter *
5511894Speter * Revision 5.16  1992/07/28  16:12:44  eggert
5611894Speter * rcs -l now asks whether you want to break the lock.
5711894Speter * Add -V.  Set RCS file's mode and time at right moment.
5811894Speter *
5911894Speter * Revision 5.15  1992/02/17  23:02:20  eggert
6011894Speter * Add -T.
6111894Speter *
6211894Speter * Revision 5.14  1992/01/27  16:42:53  eggert
6311894Speter * Add -M.  Avoid invoking umask(); it's one less thing to configure.
6411894Speter * Add support for bad_creat0.  lint -> RCS_lint
6511894Speter *
6611894Speter * Revision 5.13  1992/01/06  02:42:34  eggert
6711894Speter * Avoid changing RCS file in common cases where no change can occur.
6811894Speter *
699Sjkh * Revision 5.12  1991/11/20  17:58:08  eggert
709Sjkh * Don't read the delta tree from a nonexistent RCS file.
719Sjkh *
729Sjkh * Revision 5.11  1991/10/07  17:32:46  eggert
739Sjkh * Remove lint.
749Sjkh *
759Sjkh * Revision 5.10  1991/08/19  23:17:54  eggert
769Sjkh * Add -m, -r$, piece tables.  Revision separator is `:', not `-'.  Tune.
779Sjkh *
789Sjkh * Revision 5.9  1991/04/21  11:58:18  eggert
799Sjkh * Add -x, RCSINIT, MS-DOS support.
809Sjkh *
819Sjkh * Revision 5.8  1991/02/25  07:12:38  eggert
829Sjkh * strsave -> str_save (DG/UX name clash)
839Sjkh * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability
849Sjkh *
859Sjkh * Revision 5.7  1990/12/18  17:19:21  eggert
869Sjkh * Fix bug with multiple -n and -N options.
879Sjkh *
889Sjkh * Revision 5.6  1990/12/04  05:18:40  eggert
899Sjkh * Use -I for prompts and -q for diagnostics.
909Sjkh *
919Sjkh * Revision 5.5  1990/11/11  00:06:35  eggert
929Sjkh * Fix `rcs -e' core dump.
939Sjkh *
949Sjkh * Revision 5.4  1990/11/01  05:03:33  eggert
959Sjkh * Add -I and new -t behavior.  Permit arbitrary data in logs.
969Sjkh *
979Sjkh * Revision 5.3  1990/10/04  06:30:16  eggert
989Sjkh * Accumulate exit status across files.
999Sjkh *
1009Sjkh * Revision 5.2  1990/09/04  08:02:17  eggert
1019Sjkh * Standardize yes-or-no procedure.
1029Sjkh *
1039Sjkh * Revision 5.1  1990/08/29  07:13:51  eggert
1049Sjkh * Remove unused setuid support.  Clean old log messages too.
1059Sjkh *
1069Sjkh * Revision 5.0  1990/08/22  08:12:42  eggert
1079Sjkh * Don't lose names when applying -a option to multiple files.
1089Sjkh * Remove compile-time limits; use malloc instead.  Add setuid support.
1099Sjkh * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
1109Sjkh * Ansify and Posixate.  Add -V.  Fix umask bug.  Make linting easier.  Tune.
1119Sjkh * Yield proper exit status.  Check diff's output.
1129Sjkh *
1139Sjkh * Revision 4.11  89/05/01  15:12:06  narten
1149Sjkh * changed copyright header to reflect current distribution rules
1158858Srgrimes *
1169Sjkh * Revision 4.10  88/11/08  16:01:54  narten
1179Sjkh * didn't install previous patch correctly
1188858Srgrimes *
1199Sjkh * Revision 4.9  88/11/08  13:56:01  narten
1209Sjkh * removed include <sysexits.h> (not needed)
1219Sjkh * minor fix for -A option
1228858Srgrimes *
1239Sjkh * Revision 4.8  88/08/09  19:12:27  eggert
1249Sjkh * Don't access freed storage.
1259Sjkh * Use execv(), not system(); yield proper exit status; remove lint.
1268858Srgrimes *
1279Sjkh * Revision 4.7  87/12/18  11:37:17  narten
1289Sjkh * lint cleanups (Guy Harris)
1298858Srgrimes *
1309Sjkh * Revision 4.6  87/10/18  10:28:48  narten
1318858Srgrimes * Updating verison numbers. Changes relative to 1.1 are actually
1329Sjkh * relative to 4.3
1338858Srgrimes *
1349Sjkh * Revision 1.4  87/09/24  13:58:52  narten
1358858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf
1369Sjkh * warnings)
1378858Srgrimes *
1389Sjkh * Revision 1.3  87/03/27  14:21:55  jenkins
1399Sjkh * Port to suns
1408858Srgrimes *
1419Sjkh * Revision 1.2  85/12/17  13:59:09  albitz
1429Sjkh * Changed setstate to rcs_setstate because of conflict with random.o.
1438858Srgrimes *
1449Sjkh * Revision 4.3  83/12/15  12:27:33  wft
1459Sjkh * rcs -u now breaks most recent lock if it can't find a lock by the caller.
1468858Srgrimes *
1479Sjkh * Revision 4.2  83/12/05  10:18:20  wft
1489Sjkh * Added conditional compilation for sending mail.
1499Sjkh * Alternatives: V4_2BSD, V6, USG, and other.
1508858Srgrimes *
1519Sjkh * Revision 4.1  83/05/10  16:43:02  wft
1529Sjkh * Simplified breaklock(); added calls to findlock() and getcaller().
1539Sjkh * Added option -b (default branch). Updated -s and -w for -b.
1549Sjkh * Removed calls to stat(); now done by pairfilenames().
1559Sjkh * Replaced most catchints() calls with restoreints().
1569Sjkh * Removed check for exit status of delivermail().
1579Sjkh * Directed all interactive output to stderr.
1588858Srgrimes *
1599Sjkh * Revision 3.9.1.1  83/12/02  22:08:51  wft
1609Sjkh * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
1618858Srgrimes *
1629Sjkh * Revision 3.9  83/02/15  15:38:39  wft
1639Sjkh * Added call to fastcopy() to copy remainder of RCS file.
1649Sjkh *
1659Sjkh * Revision 3.8  83/01/18  17:37:51  wft
1669Sjkh * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
1679Sjkh *
1689Sjkh * Revision 3.7  83/01/15  18:04:25  wft
1699Sjkh * Removed putree(); replaced with puttree() in rcssyn.c.
1709Sjkh * Combined putdellog() and scanlogtext(); deleted putdellog().
1719Sjkh * Cleaned up diagnostics and error messages. Fixed problem with
1729Sjkh * mutilated files in case of deletions in 2 files in a single command.
1739Sjkh * Changed marking of selector from 'D' to DELETE.
1749Sjkh *
1759Sjkh * Revision 3.6  83/01/14  15:37:31  wft
1769Sjkh * Added ignoring of interrupts while new RCS file is renamed;
1779Sjkh * Avoids deletion of RCS files by interrupts.
1789Sjkh *
1799Sjkh * Revision 3.5  82/12/10  21:11:39  wft
1809Sjkh * Removed unused variables, fixed checking of return code from diff,
1819Sjkh * introduced variant COMPAT2 for skipping Suffix on -A files.
1829Sjkh *
1839Sjkh * Revision 3.4  82/12/04  13:18:20  wft
1849Sjkh * Replaced getdelta() with gettree(), changed breaklock to update
1859Sjkh * field lockedby, added some diagnostics.
1869Sjkh *
1879Sjkh * Revision 3.3  82/12/03  17:08:04  wft
1889Sjkh * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
1899Sjkh * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
1909Sjkh * fixed -u for missing revno. Disambiguated structure members.
1919Sjkh *
1929Sjkh * Revision 3.2  82/10/18  21:05:07  wft
1939Sjkh * rcs -i now generates a file mode given by the umask minus write permission;
1949Sjkh * otherwise, rcs keeps the mode, but removes write permission.
1959Sjkh * I added a check for write error, fixed call to getlogin(), replaced
1969Sjkh * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
1979Sjkh * conflicting, long identifiers.
1989Sjkh *
1999Sjkh * Revision 3.1  82/10/13  16:11:07  wft
2009Sjkh * fixed type of variables receiving from getc() (char -> int).
2019Sjkh */
2029Sjkh
2039Sjkh
2049Sjkh#include "rcsbase.h"
2059Sjkh
2069Sjkhstruct  Lockrev {
2079Sjkh	char const *revno;
2089Sjkh        struct  Lockrev   * nextrev;
2099Sjkh};
2109Sjkh
2119Sjkhstruct  Symrev {
2129Sjkh	char const *revno;
2139Sjkh	char const *ssymbol;
2149Sjkh        int     override;
2159Sjkh        struct  Symrev  * nextsym;
2169Sjkh};
2179Sjkh
2189Sjkhstruct Message {
2199Sjkh	char const *revno;
2209Sjkh	struct cbuf message;
2219Sjkh	struct Message *nextmessage;
2229Sjkh};
2239Sjkh
2249Sjkhstruct  Status {
2259Sjkh	char const *revno;
2269Sjkh	char const *status;
2279Sjkh        struct  Status  * nextstatus;
2289Sjkh};
2299Sjkh
2309Sjkhenum changeaccess {append, erase};
2319Sjkhstruct chaccess {
2329Sjkh	char const *login;
2339Sjkh	enum changeaccess command;
2349Sjkh	struct chaccess *nextchaccess;
2359Sjkh};
2369Sjkh
2379Sjkhstruct delrevpair {
2389Sjkh	char const *strt;
2399Sjkh	char const *end;
2409Sjkh        int     code;
2419Sjkh};
2429Sjkh
24311894Speterstatic int branchpoint P((struct hshentry*,struct hshentry*));
24411894Speterstatic int breaklock P((struct hshentry const*));
2459Sjkhstatic int buildeltatext P((struct hshentries const*));
24611894Speterstatic int doaccess P((void));
24711894Speterstatic int doassoc P((void));
24811894Speterstatic int dolocks P((void));
24911894Speterstatic int domessages P((void));
25011894Speterstatic int rcs_setstate P((char const*,char const*));
2519Sjkhstatic int removerevs P((void));
2529Sjkhstatic int sendmail P((char const*,char const*));
25311894Speterstatic int setlock P((char const*));
25411894Speterstatic struct Lockrev **rmnewlocklst P((char const*));
25511894Speterstatic struct hshentry *searchcutpt P((char const*,int,struct hshentries*));
2569Sjkhstatic void buildtree P((void));
2579Sjkhstatic void cleanup P((void));
2589Sjkhstatic void getaccessor P((char*,enum changeaccess));
2599Sjkhstatic void getassoclst P((int,char*));
2609Sjkhstatic void getchaccess P((char const*,enum changeaccess));
2619Sjkhstatic void getdelrev P((char*));
2629Sjkhstatic void getmessage P((char*));
2639Sjkhstatic void getstates P((char*));
2649Sjkhstatic void scanlogtext P((struct hshentry*,int));
2659Sjkh
2669Sjkhstatic struct buf numrev;
2679Sjkhstatic char const *headstate;
2689Sjkhstatic int chgheadstate, exitstatus, lockhead, unlockcaller;
26911894Speterstatic int suppress_mail;
2709Sjkhstatic struct Lockrev *newlocklst, *rmvlocklst;
27111894Speterstatic struct Message *messagelst, **nextmessage;
27211894Speterstatic struct Status *statelst, **nextstate;
27311894Speterstatic struct Symrev *assoclst, **nextassoc;
2749Sjkhstatic struct chaccess *chaccess, **nextchaccess;
2759Sjkhstatic struct delrevpair delrev;
2769Sjkhstatic struct hshentry *cuthead, *cuttail, *delstrt;
2779Sjkhstatic struct hshentries *gendeltas;
2789Sjkh
27911894SpetermainProg(rcsId, "rcs", "$Id: rcs.c,v 5.21 1995/06/16 06:19:24 eggert Exp $")
2809Sjkh{
2819Sjkh	static char const cmdusage[] =
28211894Speter		"\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 ...";
2839Sjkh
2849Sjkh	char *a, **newargv, *textfile;
2859Sjkh	char const *branchsym, *commsyml;
28611894Speter	int branchflag, changed, expmode, initflag;
28711894Speter	int strictlock, strict_selected, textflag;
28811894Speter	int keepRCStime, Ttimeflag;
28911894Speter	size_t commsymlen;
2909Sjkh	struct buf branchnum;
29111894Speter	struct Lockrev *lockpt;
29211894Speter	struct Lockrev **curlock, **rmvlock;
2939Sjkh        struct  Status  * curstate;
2949Sjkh
2959Sjkh	nosetid();
2969Sjkh
29711894Speter	nextassoc = &assoclst;
2989Sjkh	nextchaccess = &chaccess;
29911894Speter	nextmessage = &messagelst;
30011894Speter	nextstate = &statelst;
30111894Speter	branchsym = commsyml = textfile = 0;
3029Sjkh	branchflag = strictlock = false;
3039Sjkh	bufautobegin(&branchnum);
30411894Speter	commsymlen = 0;
30511894Speter	curlock = &newlocklst;
30611894Speter	rmvlock = &rmvlocklst;
3079Sjkh	expmode = -1;
3089Sjkh	suffixes = X_DEFAULT;
3099Sjkh        initflag= textflag = false;
3109Sjkh        strict_selected = 0;
31111894Speter	Ttimeflag = false;
3129Sjkh
3139Sjkh        /*  preprocessing command options    */
31411894Speter	if (1 < argc  &&  argv[1][0] != '-')
31511894Speter		warn("No options were given; this usage is obsolescent.");
31611894Speter
3179Sjkh	argc = getRCSINIT(argc, argv, &newargv);
3189Sjkh	argv = newargv;
3199Sjkh	while (a = *++argv,  0<--argc && *a++=='-') {
3209Sjkh		switch (*a++) {
3219Sjkh
3229Sjkh		case 'i':   /*  initial version  */
3239Sjkh                        initflag = true;
3249Sjkh                        break;
3259Sjkh
3269Sjkh                case 'b':  /* change default branch */
3279Sjkh			if (branchflag) redefined('b');
3289Sjkh                        branchflag= true;
3299Sjkh			branchsym = a;
3309Sjkh                        break;
3319Sjkh
3329Sjkh                case 'c':   /*  change comment symbol   */
3339Sjkh			if (commsyml) redefined('c');
3349Sjkh			commsyml = a;
33511894Speter			commsymlen = strlen(a);
3369Sjkh                        break;
3379Sjkh
3389Sjkh                case 'a':  /*  add new accessor   */
3399Sjkh			getaccessor(*argv+1, append);
3409Sjkh                        break;
3419Sjkh
3429Sjkh                case 'A':  /*  append access list according to accessfile  */
3439Sjkh			if (!*a) {
34411894Speter			    error("missing pathname after -A");
3459Sjkh                            break;
3469Sjkh                        }
3479Sjkh			*argv = a;
34811894Speter			if (0 < pairnames(1,argv,rcsreadopen,true,false)) {
3499Sjkh			    while (AccessList) {
3509Sjkh				getchaccess(str_save(AccessList->login),append);
3519Sjkh				AccessList = AccessList->nextaccess;
3529Sjkh			    }
3539Sjkh			    Izclose(&finptr);
3549Sjkh                        }
3559Sjkh                        break;
3569Sjkh
3579Sjkh                case 'e':    /*  remove accessors   */
3589Sjkh			getaccessor(*argv+1, erase);
3599Sjkh                        break;
3609Sjkh
3619Sjkh                case 'l':    /*   lock a revision if it is unlocked   */
3629Sjkh			if (!*a) {
3639Sjkh			    /* Lock head or default branch.  */
3649Sjkh                            lockhead = true;
3659Sjkh                            break;
3669Sjkh                        }
36711894Speter			*curlock = lockpt = talloc(struct Lockrev);
3689Sjkh			lockpt->revno = a;
36911894Speter			lockpt->nextrev = 0;
37011894Speter			curlock = &lockpt->nextrev;
3719Sjkh                        break;
3729Sjkh
3739Sjkh                case 'u':   /*  release lock of a locked revision   */
3749Sjkh			if (!*a) {
3759Sjkh                            unlockcaller=true;
3769Sjkh                            break;
3779Sjkh                        }
37811894Speter			*rmvlock = lockpt = talloc(struct Lockrev);
3799Sjkh			lockpt->revno = a;
38011894Speter			lockpt->nextrev = 0;
38111894Speter			rmvlock = &lockpt->nextrev;
38211894Speter			curlock = rmnewlocklst(lockpt->revno);
3839Sjkh                        break;
3849Sjkh
3859Sjkh                case 'L':   /*  set strict locking */
38611894Speter			if (strict_selected) {
3879Sjkh			   if (!strictlock)	  /* Already selected -U? */
38811894Speter			       warn("-U overridden by -L");
3899Sjkh                        }
3909Sjkh                        strictlock = true;
39111894Speter			strict_selected = true;
3929Sjkh                        break;
3939Sjkh
3949Sjkh                case 'U':   /*  release strict locking */
39511894Speter			if (strict_selected) {
3969Sjkh			   if (strictlock)	  /* Already selected -L? */
39711894Speter			       warn("-L overridden by -U");
3989Sjkh                        }
39911894Speter			strict_selected = true;
4009Sjkh                        break;
4019Sjkh
4029Sjkh                case 'n':    /*  add new association: error, if name exists */
4039Sjkh			if (!*a) {
4049Sjkh			    error("missing symbolic name after -n");
4059Sjkh                            break;
4069Sjkh                        }
4079Sjkh                        getassoclst(false, (*argv)+1);
4089Sjkh                        break;
4099Sjkh
4109Sjkh                case 'N':   /*  add or change association   */
4119Sjkh			if (!*a) {
4129Sjkh			    error("missing symbolic name after -N");
4139Sjkh                            break;
4149Sjkh                        }
4159Sjkh                        getassoclst(true, (*argv)+1);
4169Sjkh                        break;
4179Sjkh
4189Sjkh		case 'm':   /*  change log message  */
4199Sjkh			getmessage(a);
4209Sjkh			break;
4219Sjkh
42211894Speter		case 'M':   /*  do not send mail */
42311894Speter			suppress_mail = true;
42411894Speter			break;
42511894Speter
4269Sjkh		case 'o':   /*  delete revisions  */
4279Sjkh			if (delrev.strt) redefined('o');
4289Sjkh			if (!*a) {
4299Sjkh			    error("missing revision range after -o");
4309Sjkh                            break;
4319Sjkh                        }
4329Sjkh                        getdelrev( (*argv)+1 );
4339Sjkh                        break;
4349Sjkh
4359Sjkh                case 's':   /*  change state attribute of a revision  */
4369Sjkh			if (!*a) {
4379Sjkh			    error("state missing after -s");
4389Sjkh                            break;
4399Sjkh                        }
4409Sjkh                        getstates( (*argv)+1);
4419Sjkh                        break;
4429Sjkh
4439Sjkh                case 't':   /*  change descriptive text   */
4449Sjkh                        textflag=true;
4459Sjkh			if (*a) {
4469Sjkh				if (textfile) redefined('t');
4479Sjkh				textfile = a;
4489Sjkh                        }
4499Sjkh                        break;
4509Sjkh
45111894Speter		case 'T':  /*  do not update last-mod time for minor changes */
45211894Speter			if (*a)
45311894Speter				goto unknown;
45411894Speter			Ttimeflag = true;
45511894Speter			break;
45611894Speter
4579Sjkh		case 'I':
4589Sjkh			interactiveflag = true;
4599Sjkh			break;
4609Sjkh
4619Sjkh                case 'q':
4629Sjkh                        quietflag = true;
4639Sjkh                        break;
4649Sjkh
4659Sjkh		case 'x':
4669Sjkh			suffixes = a;
4679Sjkh			break;
4689Sjkh
4699Sjkh		case 'V':
4709Sjkh			setRCSversion(*argv);
4719Sjkh			break;
4729Sjkh
47311894Speter		case 'z':
47411894Speter			zone_set(a);
47511894Speter			break;
47611894Speter
4779Sjkh		case 'k':    /*  set keyword expand mode  */
4789Sjkh			if (0 <= expmode) redefined('k');
4799Sjkh			if (0 <= (expmode = str2expmode(a)))
4809Sjkh			    break;
4819Sjkh			/* fall into */
4829Sjkh                default:
48311894Speter		unknown:
48411894Speter			error("unknown option: %s%s", *argv, cmdusage);
4859Sjkh                };
4869Sjkh        }  /* end processing of options */
4879Sjkh
48811894Speter	/* Now handle all pathnames.  */
48911894Speter	if (nerror) cleanup();
49011894Speter	else if (argc < 1) faterror("no input file%s", cmdusage);
49111894Speter	else for (;  0 < argc;  cleanup(), ++argv, --argc) {
4929Sjkh
4939Sjkh	ffree();
4949Sjkh
4959Sjkh        if ( initflag ) {
49611894Speter	    switch (pairnames(argc, argv, rcswriteopen, false, false)) {
4979Sjkh                case -1: break;        /*  not exist; ok */
4989Sjkh                case  0: continue;     /*  error         */
49911894Speter		case  1: rcserror("already exists");
5009Sjkh                         continue;
5019Sjkh            }
5029Sjkh	}
5039Sjkh        else  {
50411894Speter	    switch (pairnames(argc, argv, rcswriteopen, true, false)) {
5059Sjkh                case -1: continue;    /*  not exist      */
5069Sjkh                case  0: continue;    /*  errors         */
5079Sjkh                case  1: break;       /*  file exists; ok*/
5089Sjkh            }
5099Sjkh	}
5109Sjkh
5119Sjkh
51211894Speter	/*
51311894Speter	 * RCSname contains the name of the RCS file, and
51411894Speter	 * workname contains the name of the working file.
5159Sjkh         * if !initflag, finptr contains the file descriptor for the
5169Sjkh         * RCS file. The admin node is initialized.
5179Sjkh         */
5189Sjkh
51911894Speter	diagnose("RCS file: %s\n", RCSname);
5209Sjkh
52111894Speter	changed = initflag | textflag;
52211894Speter	keepRCStime = Ttimeflag;
52311894Speter	if (!initflag) {
5249Sjkh		if (!checkaccesslist()) continue;
5259Sjkh		gettree(); /* Read the delta tree.  */
5269Sjkh	}
5279Sjkh
5289Sjkh        /*  update admin. node    */
52911894Speter	if (strict_selected) {
53011894Speter		changed  |=  StrictLocks ^ strictlock;
53111894Speter		StrictLocks = strictlock;
53211894Speter	}
53311894Speter	if (
53411894Speter	    commsyml &&
53511894Speter	    (
53611894Speter		commsymlen != Comment.size ||
53711894Speter		memcmp(commsyml, Comment.string, commsymlen) != 0
53811894Speter	    )
53911894Speter	) {
5409Sjkh		Comment.string = commsyml;
5419Sjkh		Comment.size = strlen(commsyml);
54211894Speter		changed = true;
5439Sjkh	}
54411894Speter	if (0 <= expmode  &&  Expand != expmode) {
54511894Speter		Expand = expmode;
54611894Speter		changed = true;
54711894Speter	}
5489Sjkh
5499Sjkh        /* update default branch */
5509Sjkh	if (branchflag && expandsym(branchsym, &branchnum)) {
5519Sjkh	    if (countnumflds(branchnum.string)) {
55211894Speter		if (cmpnum(Dbranch, branchnum.string) != 0) {
55311894Speter			Dbranch = branchnum.string;
55411894Speter			changed = true;
55511894Speter		}
5569Sjkh            } else
55711894Speter		if (Dbranch) {
55811894Speter			Dbranch = 0;
55911894Speter			changed = true;
56011894Speter		}
56111894Speter	}
5629Sjkh
56311894Speter	changed |= doaccess();	/* Update access list.  */
5649Sjkh
56511894Speter	changed |= doassoc();	/* Update association list.  */
5669Sjkh
56711894Speter	changed |= dolocks();	/* Update locks.  */
5689Sjkh
56911894Speter	changed |= domessages();	/* Update log messages.  */
5709Sjkh
5719Sjkh        /*  update state attribution  */
5729Sjkh        if (chgheadstate) {
5739Sjkh            /* change state of default branch or head */
57411894Speter	    if (!Dbranch) {
57511894Speter		if (!Head)
57611894Speter		    rcswarn("can't change states in an empty tree");
57711894Speter		else if (strcmp(Head->state, headstate) != 0) {
57811894Speter		    Head->state = headstate;
57911894Speter		    changed = true;
58011894Speter		}
58111894Speter	    } else
58211894Speter		changed |= rcs_setstate(Dbranch,headstate);
5839Sjkh        }
58411894Speter	for (curstate = statelst;  curstate;  curstate = curstate->nextstatus)
58511894Speter	    changed |= rcs_setstate(curstate->revno,curstate->status);
5869Sjkh
58711894Speter	cuthead = cuttail = 0;
5889Sjkh	if (delrev.strt && removerevs()) {
5899Sjkh            /*  rebuild delta tree if some deltas are deleted   */
5909Sjkh            if ( cuttail )
59111894Speter		VOID genrevs(
59211894Speter			cuttail->num, (char *)0, (char *)0, (char *)0,
59311894Speter			&gendeltas
59411894Speter		);
5959Sjkh            buildtree();
59611894Speter	    changed = true;
59711894Speter	    keepRCStime = false;
5989Sjkh        }
5999Sjkh
6009Sjkh	if (nerror)
6019Sjkh		continue;
6029Sjkh
60311894Speter	putadmin();
6049Sjkh        if ( Head )
6059Sjkh           puttree(Head, frewrite);
6069Sjkh	putdesc(textflag,textfile);
6079Sjkh
6089Sjkh        if ( Head) {
60911894Speter	    if (delrev.strt || messagelst) {
6109Sjkh		if (!cuttail || buildeltatext(gendeltas)) {
6119Sjkh		    advise_access(finptr, MADV_SEQUENTIAL);
61211894Speter		    scanlogtext((struct hshentry *)0, false);
6139Sjkh                    /* copy rest of delta text nodes that are not deleted      */
61411894Speter		    changed = true;
6159Sjkh		}
6169Sjkh            }
6179Sjkh        }
6189Sjkh
61911894Speter	if (initflag) {
62011894Speter		/* Adjust things for donerewrite's sake.  */
62111894Speter		if (stat(workname, &RCSstat) != 0) {
62211894Speter#		    if bad_creat0
62311894Speter			mode_t m = umask(0);
62411894Speter			(void) umask(m);
62511894Speter			RCSstat.st_mode = (S_IRUSR|S_IRGRP|S_IROTH) & ~m;
62611894Speter#		    else
62711894Speter			changed = -1;
62811894Speter#		    endif
62911894Speter		}
63011894Speter		RCSstat.st_nlink = 0;
63111894Speter		keepRCStime = false;
63211894Speter	}
63311894Speter	if (donerewrite(changed,
63411894Speter		keepRCStime ? RCSstat.st_mtime : (time_t)-1
63511894Speter	) != 0)
63611894Speter	    break;
63711894Speter
63811894Speter	diagnose("done\n");
63911894Speter	}
64011894Speter
6419Sjkh	tempunlink();
6429Sjkh	exitmain(exitstatus);
6439Sjkh}       /* end of main (rcs) */
6449Sjkh
6459Sjkh	static void
6469Sjkhcleanup()
6479Sjkh{
6489Sjkh	if (nerror) exitstatus = EXIT_FAILURE;
6499Sjkh	Izclose(&finptr);
6509Sjkh	Ozclose(&fcopy);
65111894Speter	ORCSclose();
6529Sjkh	dirtempunlink();
6539Sjkh}
6549Sjkh
65511894Speter	void
6569Sjkhexiterr()
6579Sjkh{
65811894Speter	ORCSerror();
6599Sjkh	dirtempunlink();
6609Sjkh	tempunlink();
6619Sjkh	_exit(EXIT_FAILURE);
6629Sjkh}
6639Sjkh
6649Sjkh
6659Sjkh	static void
6669Sjkhgetassoclst(flag, sp)
6679Sjkhint     flag;
6689Sjkhchar    * sp;
6699Sjkh/*  Function:   associate a symbolic name to a revision or branch,      */
6709Sjkh/*              and store in assoclst                                   */
6719Sjkh
6729Sjkh{
6739Sjkh        struct   Symrev  * pt;
6749Sjkh	char const *temp;
6759Sjkh        int                c;
6769Sjkh
67711894Speter	while ((c = *++sp) == ' ' || c == '\t' || c =='\n')
67811894Speter	    continue;
6799Sjkh        temp = sp;
68011894Speter	sp = checksym(sp, ':');  /*  check for invalid symbolic name  */
6819Sjkh	c = *sp;   *sp = '\0';
6829Sjkh        while( c == ' ' || c == '\t' || c == '\n')  c = *++sp;
6839Sjkh
6849Sjkh        if ( c != ':' && c != '\0') {
6859Sjkh	    error("invalid string %s after option -n or -N",sp);
6869Sjkh            return;
6879Sjkh        }
6889Sjkh
6899Sjkh	pt = talloc(struct Symrev);
6909Sjkh        pt->ssymbol = temp;
6919Sjkh        pt->override = flag;
6929Sjkh	if (c == '\0')  /*  delete symbol  */
69311894Speter	    pt->revno = 0;
6949Sjkh        else {
69511894Speter	    while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
69611894Speter		continue;
6979Sjkh	    pt->revno = sp;
6989Sjkh        }
69911894Speter	pt->nextsym = 0;
70011894Speter	*nextassoc = pt;
70111894Speter	nextassoc = &pt->nextsym;
7029Sjkh}
7039Sjkh
7049Sjkh
7059Sjkh	static void
7069Sjkhgetchaccess(login, command)
7079Sjkh	char const *login;
7089Sjkh	enum changeaccess command;
7099Sjkh{
7109Sjkh	register struct chaccess *pt;
7119Sjkh
71211894Speter	pt = talloc(struct chaccess);
7139Sjkh	pt->login = login;
7149Sjkh	pt->command = command;
71511894Speter	pt->nextchaccess = 0;
71611894Speter	*nextchaccess = pt;
7179Sjkh	nextchaccess = &pt->nextchaccess;
7189Sjkh}
7199Sjkh
7209Sjkh
7219Sjkh
7229Sjkh	static void
7239Sjkhgetaccessor(opt, command)
7249Sjkh	char *opt;
7259Sjkh	enum changeaccess command;
7269Sjkh/*   Function:  get the accessor list of options -e and -a,     */
7279Sjkh/*		and store in chaccess				*/
7289Sjkh
7299Sjkh
7309Sjkh{
7319Sjkh        register c;
7329Sjkh	register char *sp;
7339Sjkh
7349Sjkh	sp = opt;
73511894Speter	while ((c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',')
73611894Speter	    continue;
7379Sjkh        if ( c == '\0') {
7389Sjkh	    if (command == erase  &&  sp-opt == 1) {
73911894Speter		getchaccess((char*)0, command);
7409Sjkh		return;
7419Sjkh	    }
7429Sjkh	    error("missing login name after option -a or -e");
7439Sjkh	    return;
7449Sjkh        }
7459Sjkh
7469Sjkh        while( c != '\0') {
7479Sjkh		getchaccess(sp, command);
7489Sjkh		sp = checkid(sp,',');
7499Sjkh		c = *sp;   *sp = '\0';
7509Sjkh                while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
7519Sjkh        }
7529Sjkh}
7539Sjkh
7549Sjkh
7559Sjkh	static void
7569Sjkhgetmessage(option)
7579Sjkh	char *option;
7589Sjkh{
7599Sjkh	struct Message *pt;
7609Sjkh	struct cbuf cb;
7619Sjkh	char *m;
7629Sjkh
7639Sjkh	if (!(m = strchr(option, ':'))) {
7649Sjkh		error("-m option lacks revision number");
7659Sjkh		return;
7669Sjkh	}
7679Sjkh	*m++ = 0;
7689Sjkh	cb = cleanlogmsg(m, strlen(m));
7699Sjkh	if (!cb.size) {
7709Sjkh		error("-m option lacks log message");
7719Sjkh		return;
7729Sjkh	}
7739Sjkh	pt = talloc(struct Message);
7749Sjkh	pt->revno = option;
7759Sjkh	pt->message = cb;
7769Sjkh	pt->nextmessage = 0;
77711894Speter	*nextmessage = pt;
77811894Speter	nextmessage = &pt->nextmessage;
7799Sjkh}
7809Sjkh
7819Sjkh
7829Sjkh	static void
7839Sjkhgetstates(sp)
7849Sjkhchar    *sp;
7859Sjkh/*   Function:  get one state attribute and the corresponding   */
7869Sjkh/*              revision and store in statelst                  */
7879Sjkh
7889Sjkh{
7899Sjkh	char const *temp;
7909Sjkh        struct  Status  *pt;
7919Sjkh        register        c;
7929Sjkh
79311894Speter	while ((c = *++sp) ==' ' || c == '\t' || c == '\n')
79411894Speter	    continue;
7959Sjkh        temp = sp;
7969Sjkh	sp = checkid(sp,':');  /* check for invalid state attribute */
7979Sjkh	c = *sp;   *sp = '\0';
7989Sjkh        while( c == ' ' || c == '\t' || c == '\n' )  c = *++sp;
7999Sjkh
8009Sjkh        if ( c == '\0' ) {  /*  change state of def. branch or Head  */
8019Sjkh            chgheadstate = true;
8029Sjkh            headstate  = temp;
8039Sjkh            return;
8049Sjkh        }
8059Sjkh        else if ( c != ':' ) {
8069Sjkh	    error("missing ':' after state in option -s");
8079Sjkh            return;
8089Sjkh        }
8099Sjkh
81011894Speter	while ((c = *++sp) == ' ' || c == '\t' || c == '\n')
81111894Speter	    continue;
8129Sjkh	pt = talloc(struct Status);
8139Sjkh        pt->status     = temp;
8149Sjkh        pt->revno      = sp;
81511894Speter	pt->nextstatus = 0;
81611894Speter	*nextstate = pt;
81711894Speter	nextstate = &pt->nextstatus;
8189Sjkh}
8199Sjkh
8209Sjkh
8219Sjkh
8229Sjkh	static void
8239Sjkhgetdelrev(sp)
8249Sjkhchar    *sp;
8259Sjkh/*   Function:  get revision range or branch to be deleted,     */
8269Sjkh/*              and place in delrev                             */
8279Sjkh{
8289Sjkh        int    c;
8299Sjkh        struct  delrevpair      *pt;
8309Sjkh	int separator;
8319Sjkh
8329Sjkh	pt = &delrev;
83311894Speter	while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
83411894Speter		continue;
8359Sjkh
8369Sjkh	/* Support old ambiguous '-' syntax; this will go away.  */
8379Sjkh	if (strchr(sp,':'))
8389Sjkh		separator = ':';
8399Sjkh	else {
8409Sjkh		if (strchr(sp,'-')  &&  VERSION(5) <= RCSversion)
8419Sjkh		    warn("`-' is obsolete in `-o%s'; use `:' instead", sp);
8429Sjkh		separator = '-';
8439Sjkh	}
8449Sjkh
8459Sjkh	if (c == separator) { /* -o:rev */
84611894Speter	    while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
84711894Speter		continue;
8489Sjkh            pt->strt = sp;    pt->code = 1;
8499Sjkh            while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
8509Sjkh            *sp = '\0';
85111894Speter	    pt->end = 0;
8529Sjkh            return;
8539Sjkh        }
8549Sjkh        else {
8559Sjkh            pt->strt = sp;
8569Sjkh            while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
8579Sjkh		   && c != separator )  c = *++sp;
8589Sjkh            *sp = '\0';
8599Sjkh            while( c == ' ' || c == '\n' || c == '\t' )  c = *++sp;
8609Sjkh            if ( c == '\0' )  {  /*   -o rev or branch   */
86111894Speter		pt->code = 0;
86211894Speter		pt->end = 0;
8639Sjkh                return;
8649Sjkh            }
8659Sjkh	    if (c != separator) {
86611894Speter		error("invalid range %s %s after -o", pt->strt, sp);
8679Sjkh            }
86811894Speter	    while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
86911894Speter		continue;
8709Sjkh	    if (!c) {  /* -orev: */
87111894Speter		pt->code = 2;
87211894Speter		pt->end = 0;
8739Sjkh                return;
8749Sjkh            }
8759Sjkh        }
8769Sjkh	/* -orev1:rev2 */
8779Sjkh	pt->end = sp;  pt->code = 3;
8789Sjkh        while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
8799Sjkh        *sp = '\0';
8809Sjkh}
8819Sjkh
8829Sjkh
8839Sjkh
8849Sjkh
8859Sjkh	static void
8869Sjkhscanlogtext(delta,edit)
8879Sjkh	struct hshentry *delta;
8889Sjkh	int edit;
8899Sjkh/* Function: Scans delta text nodes up to and including the one given
89011894Speter * by delta, or up to last one present, if !delta.
89111894Speter * For the one given by delta (if delta), the log message is saved into
8929Sjkh * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied.
8939Sjkh * Assumes the initial lexeme must be read in first.
89411894Speter * Does not advance nexttok after it is finished, except if !delta.
8959Sjkh */
8969Sjkh{
8979Sjkh	struct hshentry const *nextdelta;
8989Sjkh	struct cbuf cb;
8999Sjkh
9009Sjkh	for (;;) {
9019Sjkh		foutptr = 0;
9029Sjkh		if (eoflex()) {
9039Sjkh                    if(delta)
90411894Speter			rcsfaterror("can't find delta for revision %s",
90511894Speter				delta->num
90611894Speter			);
9079Sjkh		    return; /* no more delta text nodes */
9089Sjkh                }
9099Sjkh		nextlex();
9109Sjkh		if (!(nextdelta=getnum()))
91111894Speter			fatserror("delta number corrupted");
9129Sjkh		if (nextdelta->selector) {
9139Sjkh			foutptr = frewrite;
9149Sjkh			aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
9159Sjkh                }
9169Sjkh		getkeystring(Klog);
9179Sjkh		if (nextdelta == cuttail) {
9189Sjkh			cb = savestring(&curlogbuf);
9199Sjkh			if (!delta->log.string)
9209Sjkh			    delta->log = cleanlogmsg(curlogbuf.string, cb.size);
92111894Speter			nextlex();
92211894Speter			delta->igtext = getphrases(Ktext);
92311894Speter		} else {
92411894Speter			if (nextdelta->log.string && nextdelta->selector) {
92511894Speter				foutptr = 0;
92611894Speter				readstring();
92711894Speter				foutptr = frewrite;
92811894Speter				putstring(foutptr, false, nextdelta->log, true);
92911894Speter				afputc(nextc, foutptr);
93011894Speter			} else
93111894Speter				readstring();
93211894Speter			ignorephrases(Ktext);
93311894Speter		}
9349Sjkh		getkeystring(Ktext);
9359Sjkh
9369Sjkh		if (delta==nextdelta)
9379Sjkh			break;
9389Sjkh		readstring(); /* skip over it */
9399Sjkh
9409Sjkh	}
9419Sjkh	/* got the one we're looking for */
9429Sjkh	if (edit)
94311894Speter		editstring((struct hshentry*)0);
9449Sjkh	else
9459Sjkh		enterstring();
9469Sjkh}
9479Sjkh
9489Sjkh
9499Sjkh
95011894Speter	static struct Lockrev **
9519Sjkhrmnewlocklst(which)
95211894Speter	char const *which;
95311894Speter/* Remove lock to revision WHICH from newlocklst.  */
9549Sjkh{
95511894Speter	struct Lockrev *pt, **pre;
9569Sjkh
95711894Speter	pre = &newlocklst;
95811894Speter	while ((pt = *pre))
95911894Speter	    if (strcmp(pt->revno, which) != 0)
96011894Speter		pre = &pt->nextrev;
96111894Speter	    else {
96211894Speter		*pre = pt->nextrev;
9639Sjkh		tfree(pt);
96411894Speter	    }
9659Sjkh        return pre;
9669Sjkh}
9679Sjkh
9689Sjkh
9699Sjkh
97011894Speter	static int
9719Sjkhdoaccess()
9729Sjkh{
9739Sjkh	register struct chaccess *ch;
9749Sjkh	register struct access **p, *t;
97511894Speter	register int changed = false;
9769Sjkh
9779Sjkh	for (ch = chaccess;  ch;  ch = ch->nextchaccess) {
9789Sjkh		switch (ch->command) {
9799Sjkh		case erase:
98011894Speter			if (!ch->login) {
98111894Speter			    if (AccessList) {
98211894Speter				AccessList = 0;
98311894Speter				changed = true;
98411894Speter			    }
98511894Speter			} else
98611894Speter			    for (p = &AccessList; (t = *p); p = &t->nextaccess)
98711894Speter				if (strcmp(ch->login, t->login) == 0) {
9889Sjkh					*p = t->nextaccess;
98911894Speter					changed = true;
99011894Speter					break;
99111894Speter				}
9929Sjkh			break;
9939Sjkh		case append:
9949Sjkh			for (p = &AccessList;  ;  p = &t->nextaccess)
9959Sjkh				if (!(t = *p)) {
9969Sjkh					*p = t = ftalloc(struct access);
9979Sjkh					t->login = ch->login;
99811894Speter					t->nextaccess = 0;
99911894Speter					changed = true;
10009Sjkh					break;
10019Sjkh				} else if (strcmp(ch->login, t->login) == 0)
10029Sjkh					break;
10039Sjkh			break;
10049Sjkh		}
10059Sjkh	}
100611894Speter	return changed;
10079Sjkh}
10089Sjkh
10099Sjkh
10109Sjkh	static int
10119Sjkhsendmail(Delta, who)
10129Sjkh	char const *Delta, *who;
10139Sjkh/*   Function:  mail to who, informing him that his lock on delta was
10149Sjkh *   broken by caller. Ask first whether to go ahead. Return false on
10159Sjkh *   error or if user decides not to break the lock.
10169Sjkh */
10179Sjkh{
10189Sjkh#ifdef SENDMAIL
10199Sjkh	char const *messagefile;
102011894Speter	int old1, old2, c, status;
10219Sjkh        FILE    * mailmess;
10229Sjkh#endif
10239Sjkh
10249Sjkh
10259Sjkh	aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
102611894Speter	if (suppress_mail)
102711894Speter		return true;
10289Sjkh	if (!yesorno(false, "Do you want to break the lock? [ny](n): "))
10299Sjkh		return false;
10309Sjkh
10319Sjkh        /* go ahead with breaking  */
10329Sjkh#ifdef SENDMAIL
10339Sjkh	messagefile = maketemp(0);
103411894Speter	if (!(mailmess = fopenSafer(messagefile, "w+"))) {
10359Sjkh	    efaterror(messagefile);
10369Sjkh        }
10379Sjkh
10389Sjkh	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",
103911894Speter		basefilename(RCSname), Delta, getfullRCSname(), getcaller()
10409Sjkh	);
10419Sjkh	aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr);
104211894Speter	eflush();
10439Sjkh
10449Sjkh        old1 = '\n';    old2 = ' ';
10459Sjkh        for (; ;) {
10469Sjkh	    c = getcstdin();
10479Sjkh	    if (feof(stdin)) {
10489Sjkh		aprintf(mailmess, "%c\n", old1);
10499Sjkh                break;
10509Sjkh            }
10519Sjkh            else if ( c == '\n' && old1 == '.' && old2 == '\n')
10529Sjkh                break;
10539Sjkh            else {
10549Sjkh		afputc(old1, mailmess);
10559Sjkh                old2 = old1;   old1 = c;
105611894Speter		if (c == '\n') {
105711894Speter		    aputs(">> ", stderr);
105811894Speter		    eflush();
105911894Speter		}
10609Sjkh            }
10619Sjkh        }
106211894Speter	Orewind(mailmess);
106311894Speter	aflush(mailmess);
106411894Speter	status = run(fileno(mailmess), (char*)0, SENDMAIL, who, (char*)0);
10659Sjkh	Ozclose(&mailmess);
106611894Speter	if (status == 0)
106711894Speter		return true;
106811894Speter	warn("Mail failed.");
10699Sjkh#endif
107011894Speter	warn("Mail notification of broken locks is not available.");
107111894Speter	warn("Please tell `%s' why you broke the lock.", who);
10729Sjkh	return(true);
10739Sjkh}
10749Sjkh
10759Sjkh
10769Sjkh
107711894Speter	static int
10789Sjkhbreaklock(delta)
10799Sjkh	struct hshentry const *delta;
10809Sjkh/* function: Finds the lock held by caller on delta,
10819Sjkh * and removes it.
10829Sjkh * Sends mail if a lock different from the caller's is broken.
10839Sjkh * Prints an error message if there is no such lock or error.
10849Sjkh */
10859Sjkh{
108611894Speter	register struct rcslock *next, **trail;
10879Sjkh	char const *num;
10889Sjkh
10899Sjkh	num=delta->num;
109011894Speter	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
10919Sjkh		if (strcmp(num, next->delta->num) == 0) {
10929Sjkh			if (
10939Sjkh				strcmp(getcaller(),next->login) != 0
10949Sjkh			    &&	!sendmail(num, next->login)
10959Sjkh			) {
109611894Speter			    rcserror("revision %s still locked by %s",
109711894Speter				num, next->login
109811894Speter			    );
109911894Speter			    return false;
11009Sjkh			}
110111894Speter			diagnose("%s unlocked\n", next->delta->num);
110211894Speter			*trail = next->nextlock;
110311894Speter			next->delta->lockedby = 0;
110411894Speter			return true;
11059Sjkh                }
110611894Speter	rcserror("no lock set on revision %s", num);
110711894Speter	return false;
11089Sjkh}
11099Sjkh
11109Sjkh
11119Sjkh
11129Sjkh	static struct hshentry *
11139Sjkhsearchcutpt(object, length, store)
11149Sjkh	char const *object;
111511894Speter	int length;
11169Sjkh	struct hshentries *store;
11179Sjkh/*   Function:  Search store and return entry with number being object. */
111811894Speter/*		cuttail = 0, if the entry is Head; otherwise, cuttail   */
11199Sjkh/*              is the entry point to the one with number being object  */
11209Sjkh
11219Sjkh{
112211894Speter	cuthead = 0;
11239Sjkh	while (compartial(store->first->num, object, length)) {
11249Sjkh		cuthead = store->first;
11259Sjkh		store = store->rest;
11269Sjkh	}
11279Sjkh	return store->first;
11289Sjkh}
11299Sjkh
11309Sjkh
11319Sjkh
11329Sjkh	static int
11339Sjkhbranchpoint(strt, tail)
11349Sjkhstruct  hshentry        *strt,  *tail;
11359Sjkh/*   Function: check whether the deltas between strt and tail	*/
11369Sjkh/*		are locked or branch point, return 1 if any is  */
11379Sjkh/*		locked or branch point; otherwise, return 0 and */
11389Sjkh/*		mark deleted					*/
11399Sjkh
11409Sjkh{
11419Sjkh        struct  hshentry    *pt;
114211894Speter	struct rcslock const *lockpt;
11439Sjkh
114411894Speter	for (pt = strt;  pt != tail;  pt = pt->next) {
11459Sjkh            if ( pt->branches ){ /*  a branch point  */
114611894Speter		rcserror("can't remove branch point %s", pt->num);
114711894Speter		return true;
11489Sjkh            }
114911894Speter	    for (lockpt = Locks;  lockpt;  lockpt = lockpt->nextlock)
115011894Speter		if (lockpt->delta == pt) {
115111894Speter		    rcserror("can't remove locked revision %s", pt->num);
115211894Speter		    return true;
115311894Speter		}
115411894Speter	    pt->selector = false;
115511894Speter	    diagnose("deleting revision %s\n",pt->num);
11569Sjkh        }
115711894Speter	return false;
11589Sjkh}
11599Sjkh
11609Sjkh
11619Sjkh
11629Sjkh	static int
11639Sjkhremoverevs()
11649Sjkh/*   Function:  get the revision range to be removed, and place the     */
11659Sjkh/*              first revision removed in delstrt, the revision before  */
116611894Speter/*		delstrt in cuthead (0, if delstrt is head), and the	*/
116711894Speter/*		revision after the last removed revision in cuttail (0	*/
11689Sjkh/*              if the last is a leaf                                   */
11699Sjkh
11709Sjkh{
11719Sjkh	struct  hshentry *target, *target2, *temp;
117211894Speter	int length;
117311894Speter	int cmp;
11749Sjkh
11759Sjkh	if (!expandsym(delrev.strt, &numrev)) return 0;
117611894Speter	target = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
11779Sjkh        if ( ! target ) return 0;
117811894Speter	cmp = cmpnum(target->num, numrev.string);
11799Sjkh	length = countnumflds(numrev.string);
11809Sjkh
11819Sjkh	if (delrev.code == 0) {  /*  -o  rev    or    -o  branch   */
11829Sjkh	    if (length & 1)
11839Sjkh		temp=searchcutpt(target->num,length+1,gendeltas);
118411894Speter	    else if (cmp) {
118511894Speter		rcserror("Revision %s doesn't exist.", numrev.string);
11869Sjkh		return 0;
11879Sjkh	    }
11889Sjkh	    else
11899Sjkh		temp = searchcutpt(numrev.string, length, gendeltas);
11909Sjkh	    cuttail = target->next;
11919Sjkh            if ( branchpoint(temp, cuttail) ) {
119211894Speter		cuttail = 0;
11939Sjkh                return 0;
11949Sjkh            }
11959Sjkh            delstrt = temp;     /* first revision to be removed   */
11969Sjkh            return 1;
11979Sjkh        }
11989Sjkh
11999Sjkh	if (length & 1) {   /*  invalid branch after -o   */
120011894Speter	    rcserror("invalid branch range %s after -o", numrev.string);
12019Sjkh            return 0;
12029Sjkh        }
12039Sjkh
12049Sjkh	if (delrev.code == 1) {  /*  -o  -rev   */
12059Sjkh            if ( length > 2 ) {
12069Sjkh                temp = searchcutpt( target->num, length-1, gendeltas);
12079Sjkh                cuttail = target->next;
12089Sjkh            }
12099Sjkh            else {
12109Sjkh                temp = searchcutpt(target->num, length, gendeltas);
12119Sjkh                cuttail = target;
12129Sjkh                while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
12139Sjkh                    cuttail = cuttail->next;
12149Sjkh            }
12159Sjkh            if ( branchpoint(temp, cuttail) ){
121611894Speter		cuttail = 0;
12179Sjkh                return 0;
12189Sjkh            }
12199Sjkh            delstrt = temp;
12209Sjkh            return 1;
12219Sjkh        }
12229Sjkh
12239Sjkh	if (delrev.code == 2) {   /*  -o  rev-   */
12249Sjkh            if ( length == 2 ) {
12259Sjkh                temp = searchcutpt(target->num, 1,gendeltas);
122611894Speter		if (cmp)
12279Sjkh                    cuttail = target;
12289Sjkh                else
12299Sjkh                    cuttail = target->next;
12309Sjkh            }
12319Sjkh            else  {
123211894Speter		if (cmp) {
12339Sjkh                    cuthead = target;
12349Sjkh                    if ( !(temp = target->next) ) return 0;
12359Sjkh                }
12369Sjkh                else
12379Sjkh                    temp = searchcutpt(target->num, length, gendeltas);
12389Sjkh		getbranchno(temp->num, &numrev);  /* get branch number */
123911894Speter		VOID genrevs(numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas);
12409Sjkh            }
12419Sjkh            if ( branchpoint( temp, cuttail ) ) {
124211894Speter		cuttail = 0;
12439Sjkh                return 0;
12449Sjkh            }
12459Sjkh            delstrt = temp;
12469Sjkh            return 1;
12479Sjkh        }
12489Sjkh
12499Sjkh        /*   -o   rev1-rev2   */
12509Sjkh	if (!expandsym(delrev.end, &numrev)) return 0;
12519Sjkh	if (
12529Sjkh		length != countnumflds(numrev.string)
125311894Speter	    ||	(length>2 && compartial(numrev.string, target->num, length-1))
12549Sjkh	) {
125511894Speter	    rcserror("invalid revision range %s-%s",
125611894Speter		target->num, numrev.string
125711894Speter	    );
12589Sjkh            return 0;
12599Sjkh        }
12609Sjkh
126111894Speter	target2 = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
12629Sjkh        if ( ! target2 ) return 0;
12639Sjkh
12649Sjkh        if ( length > 2) {  /* delete revisions on branches  */
12659Sjkh            if ( cmpnum(target->num, target2->num) > 0) {
126611894Speter		cmp = cmpnum(target2->num, numrev.string);
12679Sjkh                temp = target;
12689Sjkh                target = target2;
12699Sjkh                target2 = temp;
12709Sjkh            }
127111894Speter	    if (cmp) {
12729Sjkh                if ( ! cmpnum(target->num, target2->num) ) {
127311894Speter		    rcserror("Revisions %s-%s don't exist.",
127411894Speter			delrev.strt, delrev.end
127511894Speter		    );
12769Sjkh                    return 0;
12779Sjkh                }
12789Sjkh                cuthead = target;
12799Sjkh                temp = target->next;
12809Sjkh            }
12819Sjkh            else
12829Sjkh                temp = searchcutpt(target->num, length, gendeltas);
12839Sjkh            cuttail = target2->next;
12849Sjkh        }
12859Sjkh        else { /*  delete revisions on trunk  */
12869Sjkh            if ( cmpnum( target->num, target2->num) < 0 ) {
12879Sjkh                temp = target;
12889Sjkh                target = target2;
12899Sjkh                target2 = temp;
12909Sjkh            }
12919Sjkh            else
129211894Speter		cmp = cmpnum(target2->num, numrev.string);
129311894Speter	    if (cmp) {
12949Sjkh                if ( ! cmpnum(target->num, target2->num) ) {
129511894Speter		    rcserror("Revisions %s-%s don't exist.",
129611894Speter			delrev.strt, delrev.end
129711894Speter		    );
12989Sjkh                    return 0;
12999Sjkh                }
13009Sjkh                cuttail = target2;
13019Sjkh            }
13029Sjkh            else
13039Sjkh                cuttail = target2->next;
13049Sjkh            temp = searchcutpt(target->num, length, gendeltas);
13059Sjkh        }
13069Sjkh        if ( branchpoint(temp, cuttail) )  {
130711894Speter	    cuttail = 0;
13089Sjkh            return 0;
13099Sjkh        }
13109Sjkh        delstrt = temp;
13119Sjkh        return 1;
13129Sjkh}
13139Sjkh
13149Sjkh
13159Sjkh
131611894Speter	static int
13179Sjkhdoassoc()
131811894Speter/* Add or delete (if !revno) association that is stored in assoclst.  */
13199Sjkh{
13209Sjkh	char const *p;
132111894Speter	int changed = false;
13229Sjkh	struct Symrev const *curassoc;
132311894Speter	struct assoc **pre, *pt;
13249Sjkh
13259Sjkh        /*  add new associations   */
132611894Speter	for (curassoc = assoclst;  curassoc;  curassoc = curassoc->nextsym) {
132711894Speter	    char const *ssymbol = curassoc->ssymbol;
132811894Speter
132911894Speter	    if (!curassoc->revno) {  /* delete symbol  */
133011894Speter		for (pre = &Symbols;  ;  pre = &pt->nextassoc)
133111894Speter		    if (!(pt = *pre)) {
133211894Speter			rcswarn("can't delete nonexisting symbol %s", ssymbol);
133311894Speter			break;
133411894Speter		    } else if (strcmp(pt->symbol, ssymbol) == 0) {
133511894Speter			*pre = pt->nextassoc;
133611894Speter			changed = true;
133711894Speter			break;
133811894Speter		    }
13399Sjkh	    }
13409Sjkh	    else {
13419Sjkh		if (curassoc->revno[0]) {
13429Sjkh		    p = 0;
13439Sjkh		    if (expandsym(curassoc->revno, &numrev))
13449Sjkh			p = fstr_save(numrev.string);
13459Sjkh		} else if (!(p = tiprev()))
134611894Speter		    rcserror("no latest revision to associate with symbol %s",
134711894Speter			    ssymbol
13489Sjkh		    );
13499Sjkh		if (p)
135011894Speter		    changed |= addsymbol(p, ssymbol, curassoc->override);
13519Sjkh	    }
13529Sjkh        }
135311894Speter	return changed;
13549Sjkh}
13559Sjkh
13569Sjkh
13579Sjkh
135811894Speter	static int
13599Sjkhdolocks()
13609Sjkh/* Function: remove lock for caller or first lock if unlockcaller is set;
13619Sjkh *           remove locks which are stored in rmvlocklst,
13629Sjkh *           add new locks which are stored in newlocklst,
13639Sjkh *           add lock for Dbranch or Head if lockhead is set.
13649Sjkh */
13659Sjkh{
13669Sjkh	struct Lockrev const *lockpt;
13679Sjkh	struct hshentry *target;
136811894Speter	int changed = false;
13699Sjkh
13709Sjkh	if (unlockcaller) { /*  find lock for caller  */
13719Sjkh            if ( Head ) {
13729Sjkh		if (Locks) {
13739Sjkh		    switch (findlock(true, &target)) {
13749Sjkh		      case 0:
137511894Speter			/* remove most recent lock */
137611894Speter			changed |= breaklock(Locks->delta);
13779Sjkh			break;
13789Sjkh		      case 1:
13799Sjkh			diagnose("%s unlocked\n",target->num);
138011894Speter			changed = true;
13819Sjkh			break;
13829Sjkh		    }
13839Sjkh		} else {
138411894Speter		    rcswarn("No locks are set.");
13859Sjkh		}
13869Sjkh            } else {
138711894Speter		rcswarn("can't unlock an empty tree");
13889Sjkh            }
13899Sjkh        }
13909Sjkh
13919Sjkh        /*  remove locks which are stored in rmvlocklst   */
139211894Speter	for (lockpt = rmvlocklst;  lockpt;  lockpt = lockpt->nextrev)
13939Sjkh	    if (expandsym(lockpt->revno, &numrev)) {
139411894Speter		target = genrevs(numrev.string, (char *)0, (char *)0, (char *)0, &gendeltas);
13959Sjkh                if ( target )
13969Sjkh		   if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
139711894Speter			rcserror("can't unlock nonexisting revision %s",
139811894Speter				lockpt->revno
139911894Speter			);
14009Sjkh                   else
140111894Speter			changed |= breaklock(target);
14029Sjkh                        /* breaklock does its own diagnose */
14039Sjkh            }
14049Sjkh
14059Sjkh        /*  add new locks which stored in newlocklst  */
140611894Speter	for (lockpt = newlocklst;  lockpt;  lockpt = lockpt->nextrev)
140711894Speter	    changed |= setlock(lockpt->revno);
14089Sjkh
140911894Speter	if (lockhead) /*  lock default branch or head  */
141011894Speter	    if (Dbranch)
141111894Speter		changed |= setlock(Dbranch);
141211894Speter	    else if (Head)
141311894Speter		changed |= setlock(Head->num);
141411894Speter	    else
141511894Speter		rcswarn("can't lock an empty tree");
141611894Speter	return changed;
14179Sjkh}
14189Sjkh
14199Sjkh
14209Sjkh
142111894Speter	static int
14229Sjkhsetlock(rev)
14239Sjkh	char const *rev;
14249Sjkh/* Function: Given a revision or branch number, finds the corresponding
14259Sjkh * delta and locks it for caller.
14269Sjkh */
14279Sjkh{
14289Sjkh        struct  hshentry *target;
142911894Speter	int r;
14309Sjkh
14319Sjkh	if (expandsym(rev, &numrev)) {
143211894Speter	    target = genrevs(numrev.string, (char*)0, (char*)0,
143311894Speter			     (char*)0, &gendeltas);
14349Sjkh            if ( target )
14359Sjkh	       if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
143611894Speter		    rcserror("can't lock nonexisting revision %s",
143711894Speter			numrev.string
143811894Speter		    );
143911894Speter	       else {
144011894Speter		    if ((r = addlock(target, false)) < 0  &&  breaklock(target))
144111894Speter			r = addlock(target, true);
144211894Speter		    if (0 <= r) {
144311894Speter			if (r)
144411894Speter			    diagnose("%s locked\n", target->num);
144511894Speter			return r;
144611894Speter		    }
144711894Speter	       }
144811894Speter	}
144911894Speter	return 0;
14509Sjkh}
14519Sjkh
14529Sjkh
145311894Speter	static int
14549Sjkhdomessages()
14559Sjkh{
14569Sjkh	struct hshentry *target;
14579Sjkh	struct Message *p;
145811894Speter	int changed = false;
14599Sjkh
14609Sjkh	for (p = messagelst;  p;  p = p->nextmessage)
14619Sjkh	    if (
14629Sjkh		expandsym(p->revno, &numrev)  &&
14639Sjkh		(target = genrevs(
14649Sjkh			numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas
14659Sjkh		))
146611894Speter	    ) {
146711894Speter		/*
146811894Speter		 * We can't check the old log -- it's much later in the file.
146911894Speter		 * We pessimistically assume that it changed.
147011894Speter		 */
14719Sjkh		target->log = p->message;
147211894Speter		changed = true;
147311894Speter	    }
147411894Speter	return changed;
14759Sjkh}
14769Sjkh
14779Sjkh
147811894Speter	static int
14799Sjkhrcs_setstate(rev,status)
14809Sjkh	char const *rev, *status;
14819Sjkh/* Function: Given a revision or branch number, finds the corresponding delta
14829Sjkh * and sets its state to status.
14839Sjkh */
14849Sjkh{
14859Sjkh        struct  hshentry *target;
14869Sjkh
14879Sjkh	if (expandsym(rev, &numrev)) {
148811894Speter	    target = genrevs(numrev.string, (char*)0, (char*)0,
148911894Speter			     (char*)0, &gendeltas);
14909Sjkh            if ( target )
14919Sjkh	       if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
149211894Speter		    rcserror("can't set state of nonexisting revision %s",
149311894Speter			numrev.string
149411894Speter		    );
149511894Speter	       else if (strcmp(target->state, status) != 0) {
14969Sjkh                    target->state = status;
149711894Speter		    return true;
149811894Speter	       }
149911894Speter	}
150011894Speter	return false;
15019Sjkh}
15029Sjkh
15039Sjkh
15049Sjkh
15059Sjkh
15069Sjkh
15079Sjkh	static int
15089Sjkhbuildeltatext(deltas)
15099Sjkh	struct hshentries const *deltas;
15109Sjkh/*   Function:  put the delta text on frewrite and make necessary   */
15119Sjkh/*              change to delta text                                */
15129Sjkh{
15139Sjkh	register FILE *fcut;	/* temporary file to rebuild delta tree */
151411894Speter	char const *cutname;
15159Sjkh
151611894Speter	fcut = 0;
15179Sjkh	cuttail->selector = false;
15189Sjkh	scanlogtext(deltas->first, false);
15199Sjkh        if ( cuthead )  {
152011894Speter	    cutname = maketemp(3);
152111894Speter	    if (!(fcut = fopenSafer(cutname, FOPEN_WPLUS_WORK))) {
152211894Speter		efaterror(cutname);
15239Sjkh            }
15249Sjkh
15259Sjkh	    while (deltas->first != cuthead) {
15269Sjkh		deltas = deltas->rest;
15279Sjkh		scanlogtext(deltas->first, true);
15289Sjkh            }
15299Sjkh
15309Sjkh	    snapshotedit(fcut);
153111894Speter	    Orewind(fcut);
153211894Speter	    aflush(fcut);
15339Sjkh        }
15349Sjkh
15359Sjkh	while (deltas->first != cuttail)
15369Sjkh	    scanlogtext((deltas = deltas->rest)->first, true);
153711894Speter	finishedit((struct hshentry*)0, (FILE*)0, true);
15389Sjkh	Ozclose(&fcopy);
15399Sjkh
154011894Speter	if (fcut) {
154111894Speter	    char const *diffname = maketemp(0);
154211894Speter	    char const *diffv[6 + !!OPEN_O_BINARY];
154311894Speter	    char const **diffp = diffv;
154411894Speter	    *++diffp = DIFF;
154511894Speter	    *++diffp = DIFFFLAGS;
154611894Speter#	    if OPEN_O_BINARY
154711894Speter		if (Expand == BINARY_EXPAND)
154811894Speter		    *++diffp == "--binary";
154911894Speter#	    endif
155011894Speter	    *++diffp = "-";
155111894Speter	    *++diffp = resultname;
155211894Speter	    *++diffp = 0;
155311894Speter	    switch (runv(fileno(fcut), diffname, diffv)) {
15549Sjkh		case DIFF_FAILURE: case DIFF_SUCCESS: break;
155511894Speter		default: rcsfaterror("diff failed");
15569Sjkh	    }
155711894Speter	    Ofclose(fcut);
155811894Speter	    return putdtext(cuttail,diffname,frewrite,true);
15599Sjkh	} else
156011894Speter	    return putdtext(cuttail,resultname,frewrite,false);
15619Sjkh}
15629Sjkh
15639Sjkh
15649Sjkh
15659Sjkh	static void
15669Sjkhbuildtree()
15679Sjkh/*   Function:  actually removes revisions whose selector field  */
15689Sjkh/*		is false, and rebuilds the linkage of deltas.	 */
15699Sjkh/*              asks for reconfirmation if deleting last revision*/
15709Sjkh{
15719Sjkh	struct	hshentry   * Delta;
15729Sjkh        struct  branchhead      *pt, *pre;
15739Sjkh
15749Sjkh        if ( cuthead )
15759Sjkh           if ( cuthead->next == delstrt )
15769Sjkh                cuthead->next = cuttail;
15779Sjkh           else {
15789Sjkh                pre = pt = cuthead->branches;
15799Sjkh                while( pt && pt->hsh != delstrt )  {
15809Sjkh                    pre = pt;
15819Sjkh                    pt = pt->nextbranch;
15829Sjkh                }
15839Sjkh                if ( cuttail )
15849Sjkh                    pt->hsh = cuttail;
15859Sjkh                else if ( pt == pre )
15869Sjkh                    cuthead->branches = pt->nextbranch;
15879Sjkh                else
15889Sjkh                    pre->nextbranch = pt->nextbranch;
15899Sjkh            }
15909Sjkh	else {
159111894Speter	    if (!cuttail && !quietflag) {
15929Sjkh		if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) {
159311894Speter		    rcserror("No revision deleted");
15949Sjkh		    Delta = delstrt;
15959Sjkh		    while( Delta) {
15969Sjkh			Delta->selector = true;
15979Sjkh			Delta = Delta->next;
15989Sjkh		    }
15999Sjkh		    return;
16009Sjkh		}
16019Sjkh	    }
16029Sjkh            Head = cuttail;
16039Sjkh	}
16049Sjkh        return;
16059Sjkh}
16069Sjkh
160711894Speter#if RCS_lint
16089Sjkh/* This lets us lint everything all at once. */
16099Sjkh
16109Sjkhchar const cmdid[] = "";
16119Sjkh
16129Sjkh#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();}
16139Sjkh
16149Sjkh	int
16159Sjkhmain(argc, argv)
16169Sjkh	int argc;
16179Sjkh	char **argv;
16189Sjkh{
16199Sjkh	go(ciId,	ciExit);
16209Sjkh	go(coId,	coExit);
16219Sjkh	go(identId,	identExit);
16229Sjkh	go(mergeId,	mergeExit);
16239Sjkh	go(rcsId,	exiterr);
16249Sjkh	go(rcscleanId,	rcscleanExit);
16259Sjkh	go(rcsdiffId,	rdiffExit);
16269Sjkh	go(rcsmergeId,	rmergeExit);
16279Sjkh	go(rlogId,	rlogExit);
16289Sjkh	return 0;
16299Sjkh}
16309Sjkh#endif
1631