Home | History | Annotate | Line # | Download | only in src
      1 /*	$NetBSD: rcs.c,v 1.2 2016/01/14 04:22:39 christos Exp $	*/
      2 
      3 /* Change RCS file attributes.  */
      4 
      5 /* Copyright 1982, 1988, 1989 Walter Tichy
      6    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
      7    Distributed under license by the Free Software Foundation, Inc.
      8 
      9 This file is part of RCS.
     10 
     11 RCS is free software; you can redistribute it and/or modify
     12 it under the terms of the GNU General Public License as published by
     13 the Free Software Foundation; either version 2, or (at your option)
     14 any later version.
     15 
     16 RCS is distributed in the hope that it will be useful,
     17 but WITHOUT ANY WARRANTY; without even the implied warranty of
     18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     19 GNU General Public License for more details.
     20 
     21 You should have received a copy of the GNU General Public License
     22 along with RCS; see the file COPYING.
     23 If not, write to the Free Software Foundation,
     24 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
     25 
     26 Report problems and direct all questions to:
     27 
     28     rcs-bugs (at) cs.purdue.edu
     29 
     30 */
     31 
     32 /*
     33  * Log: rcs.c,v
     34  * Revision 5.21  1995/06/16 06:19:24  eggert
     35  * Update FSF address.
     36  *
     37  * Revision 5.20  1995/06/01 16:23:43  eggert
     38  * (main): Warn if no options were given.  Punctuate messages properly.
     39  *
     40  * (sendmail): Rewind mailmess before flushing it.
     41  * Output another warning if mail should work but fails.
     42  *
     43  * (buildeltatext): Pass "--binary" if -kb and if --binary makes a difference.
     44  *
     45  * Revision 5.19  1994/03/17 14:05:48  eggert
     46  * Use ORCSerror to clean up after a fatal error.  Remove lint.
     47  * Specify subprocess input via file descriptor, not file name.  Remove lint.
     48  * Flush stderr after prompt.
     49  *
     50  * Revision 5.18  1993/11/09 17:40:15  eggert
     51  * -V now prints version on stdout and exits.  Don't print usage twice.
     52  *
     53  * Revision 5.17  1993/11/03 17:42:27  eggert
     54  * Add -z.  Don't lose track of -m or -t when there are no other changes.
     55  * Don't discard ignored phrases.  Improve quality of diagnostics.
     56  *
     57  * Revision 5.16  1992/07/28  16:12:44  eggert
     58  * rcs -l now asks whether you want to break the lock.
     59  * Add -V.  Set RCS file's mode and time at right moment.
     60  *
     61  * Revision 5.15  1992/02/17  23:02:20  eggert
     62  * Add -T.
     63  *
     64  * Revision 5.14  1992/01/27  16:42:53  eggert
     65  * Add -M.  Avoid invoking umask(); it's one less thing to configure.
     66  * Add support for bad_creat0.  lint -> RCS_lint
     67  *
     68  * Revision 5.13  1992/01/06  02:42:34  eggert
     69  * Avoid changing RCS file in common cases where no change can occur.
     70  *
     71  * Revision 5.12  1991/11/20  17:58:08  eggert
     72  * Don't read the delta tree from a nonexistent RCS file.
     73  *
     74  * Revision 5.11  1991/10/07  17:32:46  eggert
     75  * Remove lint.
     76  *
     77  * Revision 5.10  1991/08/19  23:17:54  eggert
     78  * Add -m, -r$, piece tables.  Revision separator is `:', not `-'.  Tune.
     79  *
     80  * Revision 5.9  1991/04/21  11:58:18  eggert
     81  * Add -x, RCSINIT, MS-DOS support.
     82  *
     83  * Revision 5.8  1991/02/25  07:12:38  eggert
     84  * strsave -> str_save (DG/UX name clash)
     85  * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability
     86  *
     87  * Revision 5.7  1990/12/18  17:19:21  eggert
     88  * Fix bug with multiple -n and -N options.
     89  *
     90  * Revision 5.6  1990/12/04  05:18:40  eggert
     91  * Use -I for prompts and -q for diagnostics.
     92  *
     93  * Revision 5.5  1990/11/11  00:06:35  eggert
     94  * Fix `rcs -e' core dump.
     95  *
     96  * Revision 5.4  1990/11/01  05:03:33  eggert
     97  * Add -I and new -t behavior.  Permit arbitrary data in logs.
     98  *
     99  * Revision 5.3  1990/10/04  06:30:16  eggert
    100  * Accumulate exit status across files.
    101  *
    102  * Revision 5.2  1990/09/04  08:02:17  eggert
    103  * Standardize yes-or-no procedure.
    104  *
    105  * Revision 5.1  1990/08/29  07:13:51  eggert
    106  * Remove unused setuid support.  Clean old log messages too.
    107  *
    108  * Revision 5.0  1990/08/22  08:12:42  eggert
    109  * Don't lose names when applying -a option to multiple files.
    110  * Remove compile-time limits; use malloc instead.  Add setuid support.
    111  * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
    112  * Ansify and Posixate.  Add -V.  Fix umask bug.  Make linting easier.  Tune.
    113  * Yield proper exit status.  Check diff's output.
    114  *
    115  * Revision 4.11  89/05/01  15:12:06  narten
    116  * changed copyright header to reflect current distribution rules
    117  *
    118  * Revision 4.10  88/11/08  16:01:54  narten
    119  * didn't install previous patch correctly
    120  *
    121  * Revision 4.9  88/11/08  13:56:01  narten
    122  * removed include <sysexits.h> (not needed)
    123  * minor fix for -A option
    124  *
    125  * Revision 4.8  88/08/09  19:12:27  eggert
    126  * Don't access freed storage.
    127  * Use execv(), not system(); yield proper exit status; remove lint.
    128  *
    129  * Revision 4.7  87/12/18  11:37:17  narten
    130  * lint cleanups (Guy Harris)
    131  *
    132  * Revision 4.6  87/10/18  10:28:48  narten
    133  * Updating verison numbers. Changes relative to 1.1 are actually
    134  * relative to 4.3
    135  *
    136  * Revision 1.4  87/09/24  13:58:52  narten
    137  * Sources now pass through lint (if you ignore printf/sprintf/fprintf
    138  * warnings)
    139  *
    140  * Revision 1.3  87/03/27  14:21:55  jenkins
    141  * Port to suns
    142  *
    143  * Revision 1.2  85/12/17  13:59:09  albitz
    144  * Changed setstate to rcs_setstate because of conflict with random.o.
    145  *
    146  * Revision 4.3  83/12/15  12:27:33  wft
    147  * rcs -u now breaks most recent lock if it can't find a lock by the caller.
    148  *
    149  * Revision 4.2  83/12/05  10:18:20  wft
    150  * Added conditional compilation for sending mail.
    151  * Alternatives: V4_2BSD, V6, USG, and other.
    152  *
    153  * Revision 4.1  83/05/10  16:43:02  wft
    154  * Simplified breaklock(); added calls to findlock() and getcaller().
    155  * Added option -b (default branch). Updated -s and -w for -b.
    156  * Removed calls to stat(); now done by pairfilenames().
    157  * Replaced most catchints() calls with restoreints().
    158  * Removed check for exit status of delivermail().
    159  * Directed all interactive output to stderr.
    160  *
    161  * Revision 3.9.1.1  83/12/02  22:08:51  wft
    162  * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
    163  *
    164  * Revision 3.9  83/02/15  15:38:39  wft
    165  * Added call to fastcopy() to copy remainder of RCS file.
    166  *
    167  * Revision 3.8  83/01/18  17:37:51  wft
    168  * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
    169  *
    170  * Revision 3.7  83/01/15  18:04:25  wft
    171  * Removed putree(); replaced with puttree() in rcssyn.c.
    172  * Combined putdellog() and scanlogtext(); deleted putdellog().
    173  * Cleaned up diagnostics and error messages. Fixed problem with
    174  * mutilated files in case of deletions in 2 files in a single command.
    175  * Changed marking of selector from 'D' to DELETE.
    176  *
    177  * Revision 3.6  83/01/14  15:37:31  wft
    178  * Added ignoring of interrupts while new RCS file is renamed;
    179  * Avoids deletion of RCS files by interrupts.
    180  *
    181  * Revision 3.5  82/12/10  21:11:39  wft
    182  * Removed unused variables, fixed checking of return code from diff,
    183  * introduced variant COMPAT2 for skipping Suffix on -A files.
    184  *
    185  * Revision 3.4  82/12/04  13:18:20  wft
    186  * Replaced getdelta() with gettree(), changed breaklock to update
    187  * field lockedby, added some diagnostics.
    188  *
    189  * Revision 3.3  82/12/03  17:08:04  wft
    190  * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
    191  * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
    192  * fixed -u for missing revno. Disambiguated structure members.
    193  *
    194  * Revision 3.2  82/10/18  21:05:07  wft
    195  * rcs -i now generates a file mode given by the umask minus write permission;
    196  * otherwise, rcs keeps the mode, but removes write permission.
    197  * I added a check for write error, fixed call to getlogin(), replaced
    198  * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
    199  * conflicting, long identifiers.
    200  *
    201  * Revision 3.1  82/10/13  16:11:07  wft
    202  * fixed type of variables receiving from getc() (char -> int).
    203  */
    204 
    205 
    206 #include "rcsbase.h"
    207 
    208 struct  Lockrev {
    209 	char const *revno;
    210         struct  Lockrev   * nextrev;
    211 };
    212 
    213 struct  Symrev {
    214 	char const *revno;
    215 	char const *ssymbol;
    216         int     override;
    217         struct  Symrev  * nextsym;
    218 };
    219 
    220 struct Message {
    221 	char const *revno;
    222 	struct cbuf message;
    223 	struct Message *nextmessage;
    224 };
    225 
    226 struct  Status {
    227 	char const *revno;
    228 	char const *status;
    229         struct  Status  * nextstatus;
    230 };
    231 
    232 enum changeaccess {append, erase};
    233 struct chaccess {
    234 	char const *login;
    235 	enum changeaccess command;
    236 	struct chaccess *nextchaccess;
    237 };
    238 
    239 struct delrevpair {
    240 	char const *strt;
    241 	char const *end;
    242         int     code;
    243 };
    244 
    245 static int branchpoint P((struct hshentry*,struct hshentry*));
    246 static int breaklock P((struct hshentry const*));
    247 static int buildeltatext P((struct hshentries const*));
    248 static int doaccess P((void));
    249 static int doassoc P((void));
    250 static int dolocks P((void));
    251 static int domessages P((void));
    252 static int rcs_setstate P((char const*,char const*));
    253 static int removerevs P((void));
    254 static int sendmail P((char const*,char const*));
    255 static int setlock P((char const*));
    256 static struct Lockrev **rmnewlocklst P((char const*));
    257 static struct hshentry *searchcutpt P((char const*,int,struct hshentries*));
    258 static void buildtree P((void));
    259 static void cleanup P((void));
    260 static void getaccessor P((char*,enum changeaccess));
    261 static void getassoclst P((int,char*));
    262 static void getchaccess P((char const*,enum changeaccess));
    263 static void getdelrev P((char*));
    264 static void getmessage P((char*));
    265 static void getstates P((char*));
    266 static void scanlogtext P((struct hshentry*,int));
    267 
    268 static struct buf numrev;
    269 static char const *headstate;
    270 static int chgheadstate, exitstatus, lockhead, unlockcaller;
    271 static int suppress_mail;
    272 static struct Lockrev *newlocklst, *rmvlocklst;
    273 static struct Message *messagelst, **nextmessage;
    274 static struct Status *statelst, **nextstate;
    275 static struct Symrev *assoclst, **nextassoc;
    276 static struct chaccess *chaccess, **nextchaccess;
    277 static struct delrevpair delrev;
    278 static struct hshentry *cuthead, *cuttail, *delstrt;
    279 static struct hshentries *gendeltas;
    280 
    281 mainProg(rcsId, "rcs", "Id: rcs.c,v 5.21 1995/06/16 06:19:24 eggert Exp ")
    282 {
    283 	static char const cmdusage[] =
    284 		"\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 ...";
    285 
    286 	char *a, **newargv, *textfile;
    287 	char const *branchsym, *commsyml;
    288 	int branchflag, changed, expmode, initflag;
    289 	int strictlock, strict_selected, textflag;
    290 	int keepRCStime, Ttimeflag;
    291 	size_t commsymlen;
    292 	struct buf branchnum;
    293 	struct Lockrev *lockpt;
    294 	struct Lockrev **curlock, **rmvlock;
    295         struct  Status  * curstate;
    296 
    297 	nosetid();
    298 
    299 	nextassoc = &assoclst;
    300 	nextchaccess = &chaccess;
    301 	nextmessage = &messagelst;
    302 	nextstate = &statelst;
    303 	branchsym = commsyml = textfile = 0;
    304 	branchflag = strictlock = false;
    305 	bufautobegin(&branchnum);
    306 	commsymlen = 0;
    307 	curlock = &newlocklst;
    308 	rmvlock = &rmvlocklst;
    309 	expmode = -1;
    310 	suffixes = X_DEFAULT;
    311         initflag= textflag = false;
    312         strict_selected = 0;
    313 	Ttimeflag = false;
    314 
    315         /*  preprocessing command options    */
    316 	if (1 < argc  &&  argv[1][0] != '-')
    317 		warn("No options were given; this usage is obsolescent.");
    318 
    319 	argc = getRCSINIT(argc, argv, &newargv);
    320 	argv = newargv;
    321 	while (a = *++argv,  0<--argc && *a++=='-') {
    322 		switch (*a++) {
    323 
    324 		case 'i':   /*  initial version  */
    325                         initflag = true;
    326                         break;
    327 
    328                 case 'b':  /* change default branch */
    329 			if (branchflag) redefined('b');
    330                         branchflag= true;
    331 			branchsym = a;
    332                         break;
    333 
    334                 case 'c':   /*  change comment symbol   */
    335 			if (commsyml) redefined('c');
    336 			commsyml = a;
    337 			commsymlen = strlen(a);
    338                         break;
    339 
    340                 case 'a':  /*  add new accessor   */
    341 			getaccessor(*argv+1, append);
    342                         break;
    343 
    344                 case 'A':  /*  append access list according to accessfile  */
    345 			if (!*a) {
    346 			    error("missing pathname after -A");
    347                             break;
    348                         }
    349 			*argv = a;
    350 			if (0 < pairnames(1,argv,rcsreadopen,true,false)) {
    351 			    while (AccessList) {
    352 				getchaccess(str_save(AccessList->login),append);
    353 				AccessList = AccessList->nextaccess;
    354 			    }
    355 			    Izclose(&finptr);
    356                         }
    357                         break;
    358 
    359                 case 'e':    /*  remove accessors   */
    360 			getaccessor(*argv+1, erase);
    361                         break;
    362 
    363                 case 'l':    /*   lock a revision if it is unlocked   */
    364 			if (!*a) {
    365 			    /* Lock head or default branch.  */
    366                             lockhead = true;
    367                             break;
    368                         }
    369 			*curlock = lockpt = talloc(struct Lockrev);
    370 			lockpt->revno = a;
    371 			lockpt->nextrev = 0;
    372 			curlock = &lockpt->nextrev;
    373                         break;
    374 
    375                 case 'u':   /*  release lock of a locked revision   */
    376 			if (!*a) {
    377                             unlockcaller=true;
    378                             break;
    379                         }
    380 			*rmvlock = lockpt = talloc(struct Lockrev);
    381 			lockpt->revno = a;
    382 			lockpt->nextrev = 0;
    383 			rmvlock = &lockpt->nextrev;
    384 			curlock = rmnewlocklst(lockpt->revno);
    385                         break;
    386 
    387                 case 'L':   /*  set strict locking */
    388 			if (strict_selected) {
    389 			   if (!strictlock)	  /* Already selected -U? */
    390 			       warn("-U overridden by -L");
    391                         }
    392                         strictlock = true;
    393 			strict_selected = true;
    394                         break;
    395 
    396                 case 'U':   /*  release strict locking */
    397 			if (strict_selected) {
    398 			   if (strictlock)	  /* Already selected -L? */
    399 			       warn("-L overridden by -U");
    400                         }
    401 			strict_selected = true;
    402                         break;
    403 
    404                 case 'n':    /*  add new association: error, if name exists */
    405 			if (!*a) {
    406 			    error("missing symbolic name after -n");
    407                             break;
    408                         }
    409                         getassoclst(false, (*argv)+1);
    410                         break;
    411 
    412                 case 'N':   /*  add or change association   */
    413 			if (!*a) {
    414 			    error("missing symbolic name after -N");
    415                             break;
    416                         }
    417                         getassoclst(true, (*argv)+1);
    418                         break;
    419 
    420 		case 'm':   /*  change log message  */
    421 			getmessage(a);
    422 			break;
    423 
    424 		case 'M':   /*  do not send mail */
    425 			suppress_mail = true;
    426 			break;
    427 
    428 		case 'o':   /*  delete revisions  */
    429 			if (delrev.strt) redefined('o');
    430 			if (!*a) {
    431 			    error("missing revision range after -o");
    432                             break;
    433                         }
    434                         getdelrev( (*argv)+1 );
    435                         break;
    436 
    437                 case 's':   /*  change state attribute of a revision  */
    438 			if (!*a) {
    439 			    error("state missing after -s");
    440                             break;
    441                         }
    442                         getstates( (*argv)+1);
    443                         break;
    444 
    445                 case 't':   /*  change descriptive text   */
    446                         textflag=true;
    447 			if (*a) {
    448 				if (textfile) redefined('t');
    449 				textfile = a;
    450                         }
    451                         break;
    452 
    453 		case 'T':  /*  do not update last-mod time for minor changes */
    454 			if (*a)
    455 				goto unknown;
    456 			Ttimeflag = true;
    457 			break;
    458 
    459 		case 'I':
    460 			interactiveflag = true;
    461 			break;
    462 
    463                 case 'q':
    464                         quietflag = true;
    465                         break;
    466 
    467 		case 'x':
    468 			suffixes = a;
    469 			break;
    470 
    471 		case 'V':
    472 			setRCSversion(*argv);
    473 			break;
    474 
    475 		case 'z':
    476 			zone_set(a);
    477 			break;
    478 
    479 		case 'k':    /*  set keyword expand mode  */
    480 			if (0 <= expmode) redefined('k');
    481 			if (0 <= (expmode = str2expmode(a)))
    482 			    break;
    483 			/* fall into */
    484                 default:
    485 		unknown:
    486 			error("unknown option: %s%s", *argv, cmdusage);
    487                 };
    488         }  /* end processing of options */
    489 
    490 	/* Now handle all pathnames.  */
    491 	if (nerror) cleanup();
    492 	else if (argc < 1) faterror("no input file%s", cmdusage);
    493 	else for (;  0 < argc;  cleanup(), ++argv, --argc) {
    494 
    495 	ffree();
    496 
    497         if ( initflag ) {
    498 	    switch (pairnames(argc, argv, rcswriteopen, false, false)) {
    499                 case -1: break;        /*  not exist; ok */
    500                 case  0: continue;     /*  error         */
    501 		case  1: rcserror("already exists");
    502                          continue;
    503             }
    504 	}
    505         else  {
    506 	    switch (pairnames(argc, argv, rcswriteopen, true, false)) {
    507                 case -1: continue;    /*  not exist      */
    508                 case  0: continue;    /*  errors         */
    509                 case  1: break;       /*  file exists; ok*/
    510             }
    511 	}
    512 
    513 
    514 	/*
    515 	 * RCSname contains the name of the RCS file, and
    516 	 * workname contains the name of the working file.
    517          * if !initflag, finptr contains the file descriptor for the
    518          * RCS file. The admin node is initialized.
    519          */
    520 
    521 	diagnose("RCS file: %s\n", RCSname);
    522 
    523 	changed = initflag | textflag;
    524 	keepRCStime = Ttimeflag;
    525 	if (!initflag) {
    526 		if (!checkaccesslist()) continue;
    527 		gettree(); /* Read the delta tree.  */
    528 	}
    529 
    530         /*  update admin. node    */
    531 	if (strict_selected) {
    532 		changed  |=  StrictLocks ^ strictlock;
    533 		StrictLocks = strictlock;
    534 	}
    535 	if (
    536 	    commsyml &&
    537 	    (
    538 		commsymlen != Comment.size ||
    539 		memcmp(commsyml, Comment.string, commsymlen) != 0
    540 	    )
    541 	) {
    542 		Comment.string = commsyml;
    543 		Comment.size = strlen(commsyml);
    544 		changed = true;
    545 	}
    546 	if (0 <= expmode  &&  Expand != expmode) {
    547 		Expand = expmode;
    548 		changed = true;
    549 	}
    550 
    551         /* update default branch */
    552 	if (branchflag && expandsym(branchsym, &branchnum)) {
    553 	    if (countnumflds(branchnum.string)) {
    554 		if (cmpnum(Dbranch, branchnum.string) != 0) {
    555 			Dbranch = branchnum.string;
    556 			changed = true;
    557 		}
    558             } else
    559 		if (Dbranch) {
    560 			Dbranch = 0;
    561 			changed = true;
    562 		}
    563 	}
    564 
    565 	changed |= doaccess();	/* Update access list.  */
    566 
    567 	changed |= doassoc();	/* Update association list.  */
    568 
    569 	changed |= dolocks();	/* Update locks.  */
    570 
    571 	changed |= domessages();	/* Update log messages.  */
    572 
    573         /*  update state attribution  */
    574         if (chgheadstate) {
    575             /* change state of default branch or head */
    576 	    if (!Dbranch) {
    577 		if (!Head)
    578 		    rcswarn("can't change states in an empty tree");
    579 		else if (strcmp(Head->state, headstate) != 0) {
    580 		    Head->state = headstate;
    581 		    changed = true;
    582 		}
    583 	    } else
    584 		changed |= rcs_setstate(Dbranch,headstate);
    585         }
    586 	for (curstate = statelst;  curstate;  curstate = curstate->nextstatus)
    587 	    changed |= rcs_setstate(curstate->revno,curstate->status);
    588 
    589 	cuthead = cuttail = 0;
    590 	if (delrev.strt && removerevs()) {
    591             /*  rebuild delta tree if some deltas are deleted   */
    592             if ( cuttail )
    593 		VOID genrevs(
    594 			cuttail->num, (char *)0, (char *)0, (char *)0,
    595 			&gendeltas
    596 		);
    597             buildtree();
    598 	    changed = true;
    599 	    keepRCStime = false;
    600         }
    601 
    602 	if (nerror)
    603 		continue;
    604 
    605 	putadmin();
    606         if ( Head )
    607            puttree(Head, frewrite);
    608 	putdesc(textflag,textfile);
    609 
    610         if ( Head) {
    611 	    if (delrev.strt || messagelst) {
    612 		if (!cuttail || buildeltatext(gendeltas)) {
    613 		    advise_access(finptr, MADV_SEQUENTIAL);
    614 		    scanlogtext((struct hshentry *)0, false);
    615                     /* copy rest of delta text nodes that are not deleted      */
    616 		    changed = true;
    617 		}
    618             }
    619         }
    620 
    621 	if (initflag) {
    622 		/* Adjust things for donerewrite's sake.  */
    623 		if (stat(workname, &RCSstat) != 0) {
    624 #		    if bad_creat0
    625 			mode_t m = umask(0);
    626 			(void) umask(m);
    627 			RCSstat.st_mode = (S_IRUSR|S_IRGRP|S_IROTH) & ~m;
    628 #		    else
    629 			changed = -1;
    630 #		    endif
    631 		}
    632 		RCSstat.st_nlink = 0;
    633 		keepRCStime = false;
    634 	}
    635 	if (donerewrite(changed,
    636 		keepRCStime ? RCSstat.st_mtime : (time_t)-1
    637 	) != 0)
    638 	    break;
    639 
    640 	diagnose("done\n");
    641 	}
    642 
    643 	tempunlink();
    644 	exitmain(exitstatus);
    645 }       /* end of main (rcs) */
    646 
    647 	static void
    648 cleanup()
    649 {
    650 	if (nerror) exitstatus = EXIT_FAILURE;
    651 	Izclose(&finptr);
    652 	Ozclose(&fcopy);
    653 	ORCSclose();
    654 	dirtempunlink();
    655 }
    656 
    657 	void
    658 exiterr()
    659 {
    660 	ORCSerror();
    661 	dirtempunlink();
    662 	tempunlink();
    663 	_exit(EXIT_FAILURE);
    664 }
    665 
    666 
    667 	static void
    668 getassoclst(flag, sp)
    669 int     flag;
    670 char    * sp;
    671 /*  Function:   associate a symbolic name to a revision or branch,      */
    672 /*              and store in assoclst                                   */
    673 
    674 {
    675         struct   Symrev  * pt;
    676 	char const *temp;
    677         int                c;
    678 
    679 	while ((c = *++sp) == ' ' || c == '\t' || c =='\n')
    680 	    continue;
    681         temp = sp;
    682 	sp = checksym(sp, ':');  /*  check for invalid symbolic name  */
    683 	c = *sp;   *sp = '\0';
    684         while( c == ' ' || c == '\t' || c == '\n')  c = *++sp;
    685 
    686         if ( c != ':' && c != '\0') {
    687 	    error("invalid string %s after option -n or -N",sp);
    688             return;
    689         }
    690 
    691 	pt = talloc(struct Symrev);
    692         pt->ssymbol = temp;
    693         pt->override = flag;
    694 	if (c == '\0')  /*  delete symbol  */
    695 	    pt->revno = 0;
    696         else {
    697 	    while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
    698 		continue;
    699 	    pt->revno = sp;
    700         }
    701 	pt->nextsym = 0;
    702 	*nextassoc = pt;
    703 	nextassoc = &pt->nextsym;
    704 }
    705 
    706 
    707 	static void
    708 getchaccess(login, command)
    709 	char const *login;
    710 	enum changeaccess command;
    711 {
    712 	register struct chaccess *pt;
    713 
    714 	pt = talloc(struct chaccess);
    715 	pt->login = login;
    716 	pt->command = command;
    717 	pt->nextchaccess = 0;
    718 	*nextchaccess = pt;
    719 	nextchaccess = &pt->nextchaccess;
    720 }
    721 
    722 
    723 
    724 	static void
    725 getaccessor(opt, command)
    726 	char *opt;
    727 	enum changeaccess command;
    728 /*   Function:  get the accessor list of options -e and -a,     */
    729 /*		and store in chaccess				*/
    730 
    731 
    732 {
    733         int c;
    734 	char *sp;
    735 
    736 	sp = opt;
    737 	while ((c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',')
    738 	    continue;
    739         if ( c == '\0') {
    740 	    if (command == erase  &&  sp-opt == 1) {
    741 		getchaccess((char*)0, command);
    742 		return;
    743 	    }
    744 	    error("missing login name after option -a or -e");
    745 	    return;
    746         }
    747 
    748         while( c != '\0') {
    749 		getchaccess(sp, command);
    750 		sp = checkid(sp,',');
    751 		c = *sp;   *sp = '\0';
    752                 while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
    753         }
    754 }
    755 
    756 
    757 	static void
    758 getmessage(option)
    759 	char *option;
    760 {
    761 	struct Message *pt;
    762 	struct cbuf cb;
    763 	char *m;
    764 
    765 	if (!(m = strchr(option, ':'))) {
    766 		error("-m option lacks revision number");
    767 		return;
    768 	}
    769 	*m++ = 0;
    770 	cb = cleanlogmsg(m, strlen(m));
    771 	if (!cb.size) {
    772 		error("-m option lacks log message");
    773 		return;
    774 	}
    775 	pt = talloc(struct Message);
    776 	pt->revno = option;
    777 	pt->message = cb;
    778 	pt->nextmessage = 0;
    779 	*nextmessage = pt;
    780 	nextmessage = &pt->nextmessage;
    781 }
    782 
    783 
    784 	static void
    785 getstates(sp)
    786 char    *sp;
    787 /*   Function:  get one state attribute and the corresponding   */
    788 /*              revision and store in statelst                  */
    789 
    790 {
    791 	char const *temp;
    792         struct  Status  *pt;
    793         int c;
    794 
    795 	while ((c = *++sp) ==' ' || c == '\t' || c == '\n')
    796 	    continue;
    797         temp = sp;
    798 	sp = checkid(sp,':');  /* check for invalid state attribute */
    799 	c = *sp;   *sp = '\0';
    800         while( c == ' ' || c == '\t' || c == '\n' )  c = *++sp;
    801 
    802         if ( c == '\0' ) {  /*  change state of def. branch or Head  */
    803             chgheadstate = true;
    804             headstate  = temp;
    805             return;
    806         }
    807         else if ( c != ':' ) {
    808 	    error("missing ':' after state in option -s");
    809             return;
    810         }
    811 
    812 	while ((c = *++sp) == ' ' || c == '\t' || c == '\n')
    813 	    continue;
    814 	pt = talloc(struct Status);
    815         pt->status     = temp;
    816         pt->revno      = sp;
    817 	pt->nextstatus = 0;
    818 	*nextstate = pt;
    819 	nextstate = &pt->nextstatus;
    820 }
    821 
    822 
    823 
    824 	static void
    825 getdelrev(sp)
    826 char    *sp;
    827 /*   Function:  get revision range or branch to be deleted,     */
    828 /*              and place in delrev                             */
    829 {
    830         int    c;
    831         struct  delrevpair      *pt;
    832 	int separator;
    833 
    834 	pt = &delrev;
    835 	while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
    836 		continue;
    837 
    838 	/* Support old ambiguous '-' syntax; this will go away.  */
    839 	if (strchr(sp,':'))
    840 		separator = ':';
    841 	else {
    842 		if (strchr(sp,'-')  &&  VERSION(5) <= RCSversion)
    843 		    warn("`-' is obsolete in `-o%s'; use `:' instead", sp);
    844 		separator = '-';
    845 	}
    846 
    847 	if (c == separator) { /* -o:rev */
    848 	    while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t')
    849 		continue;
    850             pt->strt = sp;    pt->code = 1;
    851             while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
    852             *sp = '\0';
    853 	    pt->end = 0;
    854             return;
    855         }
    856         else {
    857             pt->strt = sp;
    858             while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
    859 		   && c != separator )  c = *++sp;
    860             *sp = '\0';
    861             while( c == ' ' || c == '\n' || c == '\t' )  c = *++sp;
    862             if ( c == '\0' )  {  /*   -o rev or branch   */
    863 		pt->code = 0;
    864 		pt->end = 0;
    865                 return;
    866             }
    867 	    if (c != separator) {
    868 		error("invalid range %s %s after -o", pt->strt, sp);
    869             }
    870 	    while ((c = *++sp) == ' ' || c == '\n' || c == '\t')
    871 		continue;
    872 	    if (!c) {  /* -orev: */
    873 		pt->code = 2;
    874 		pt->end = 0;
    875                 return;
    876             }
    877         }
    878 	/* -orev1:rev2 */
    879 	pt->end = sp;  pt->code = 3;
    880         while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
    881         *sp = '\0';
    882 }
    883 
    884 
    885 
    886 
    887 	static void
    888 scanlogtext(delta,edit)
    889 	struct hshentry *delta;
    890 	int edit;
    891 /* Function: Scans delta text nodes up to and including the one given
    892  * by delta, or up to last one present, if !delta.
    893  * For the one given by delta (if delta), the log message is saved into
    894  * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied.
    895  * Assumes the initial lexeme must be read in first.
    896  * Does not advance nexttok after it is finished, except if !delta.
    897  */
    898 {
    899 	struct hshentry const *nextdelta;
    900 	struct cbuf cb;
    901 
    902 	for (;;) {
    903 		foutptr = 0;
    904 		if (eoflex()) {
    905                     if(delta)
    906 			rcsfaterror("can't find delta for revision %s",
    907 				delta->num
    908 			);
    909 		    return; /* no more delta text nodes */
    910                 }
    911 		nextlex();
    912 		if (!(nextdelta=getnum()))
    913 			fatserror("delta number corrupted");
    914 		if (nextdelta->selector) {
    915 			foutptr = frewrite;
    916 			aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
    917                 }
    918 		getkeystring(Klog);
    919 		if (nextdelta == cuttail) {
    920 			cb = savestring(&curlogbuf);
    921 			if (!delta->log.string)
    922 			    delta->log = cleanlogmsg(curlogbuf.string, cb.size);
    923 			nextlex();
    924 			delta->igtext = getphrases(Ktext);
    925 		} else {
    926 			if (nextdelta->log.string && nextdelta->selector) {
    927 				foutptr = 0;
    928 				readstring();
    929 				foutptr = frewrite;
    930 				putstring(foutptr, false, nextdelta->log, true);
    931 				afputc(nextc, foutptr);
    932 			} else
    933 				readstring();
    934 			ignorephrases(Ktext);
    935 		}
    936 		getkeystring(Ktext);
    937 
    938 		if (delta==nextdelta)
    939 			break;
    940 		readstring(); /* skip over it */
    941 
    942 	}
    943 	/* got the one we're looking for */
    944 	if (edit)
    945 		editstring((struct hshentry*)0);
    946 	else
    947 		enterstring();
    948 }
    949 
    950 
    951 
    952 	static struct Lockrev **
    953 rmnewlocklst(which)
    954 	char const *which;
    955 /* Remove lock to revision WHICH from newlocklst.  */
    956 {
    957 	struct Lockrev *pt, **pre;
    958 
    959 	pre = &newlocklst;
    960 	while ((pt = *pre))
    961 	    if (strcmp(pt->revno, which) != 0)
    962 		pre = &pt->nextrev;
    963 	    else {
    964 		*pre = pt->nextrev;
    965 		tfree(pt);
    966 	    }
    967         return pre;
    968 }
    969 
    970 
    971 
    972 	static int
    973 doaccess()
    974 {
    975 	register struct chaccess *ch;
    976 	register struct access **p, *t;
    977 	register int changed = false;
    978 
    979 	for (ch = chaccess;  ch;  ch = ch->nextchaccess) {
    980 		switch (ch->command) {
    981 		case erase:
    982 			if (!ch->login) {
    983 			    if (AccessList) {
    984 				AccessList = 0;
    985 				changed = true;
    986 			    }
    987 			} else
    988 			    for (p = &AccessList; (t = *p); p = &t->nextaccess)
    989 				if (strcmp(ch->login, t->login) == 0) {
    990 					*p = t->nextaccess;
    991 					changed = true;
    992 					break;
    993 				}
    994 			break;
    995 		case append:
    996 			for (p = &AccessList;  ;  p = &t->nextaccess)
    997 				if (!(t = *p)) {
    998 					*p = t = ftalloc(struct access);
    999 					t->login = ch->login;
   1000 					t->nextaccess = 0;
   1001 					changed = true;
   1002 					break;
   1003 				} else if (strcmp(ch->login, t->login) == 0)
   1004 					break;
   1005 			break;
   1006 		}
   1007 	}
   1008 	return changed;
   1009 }
   1010 
   1011 
   1012 	static int
   1013 sendmail(Delta, who)
   1014 	char const *Delta, *who;
   1015 /*   Function:  mail to who, informing him that his lock on delta was
   1016  *   broken by caller. Ask first whether to go ahead. Return false on
   1017  *   error or if user decides not to break the lock.
   1018  */
   1019 {
   1020 #ifdef SENDMAIL
   1021 	char const *messagefile;
   1022 	int old1, old2, c, status;
   1023         FILE    * mailmess;
   1024 #endif
   1025 
   1026 
   1027 	aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
   1028 	if (suppress_mail)
   1029 		return true;
   1030 	if (!yesorno(false, "Do you want to break the lock? [ny](n): "))
   1031 		return false;
   1032 
   1033         /* go ahead with breaking  */
   1034 #ifdef SENDMAIL
   1035 	messagefile = maketemp(0);
   1036 	if (!(mailmess = fopenSafer(messagefile, "w+"))) {
   1037 	    efaterror(messagefile);
   1038         }
   1039 
   1040 	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",
   1041 		basefilename(RCSname), Delta, getfullRCSname(), getcaller()
   1042 	);
   1043 	aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr);
   1044 	eflush();
   1045 
   1046         old1 = '\n';    old2 = ' ';
   1047         for (; ;) {
   1048 	    c = getcstdin();
   1049 	    if (feof(stdin)) {
   1050 		aprintf(mailmess, "%c\n", old1);
   1051                 break;
   1052             }
   1053             else if ( c == '\n' && old1 == '.' && old2 == '\n')
   1054                 break;
   1055             else {
   1056 		afputc(old1, mailmess);
   1057                 old2 = old1;   old1 = c;
   1058 		if (c == '\n') {
   1059 		    aputs(">> ", stderr);
   1060 		    eflush();
   1061 		}
   1062             }
   1063         }
   1064 	Orewind(mailmess);
   1065 	aflush(mailmess);
   1066 	status = run(fileno(mailmess), (char*)0, SENDMAIL, who, (char*)0);
   1067 	Ozclose(&mailmess);
   1068 	if (status == 0)
   1069 		return true;
   1070 	warn("Mail failed.");
   1071 #endif
   1072 	warn("Mail notification of broken locks is not available.");
   1073 	warn("Please tell `%s' why you broke the lock.", who);
   1074 	return(true);
   1075 }
   1076 
   1077 
   1078 
   1079 	static int
   1080 breaklock(delta)
   1081 	struct hshentry const *delta;
   1082 /* function: Finds the lock held by caller on delta,
   1083  * and removes it.
   1084  * Sends mail if a lock different from the caller's is broken.
   1085  * Prints an error message if there is no such lock or error.
   1086  */
   1087 {
   1088 	register struct rcslock *next, **trail;
   1089 	char const *num;
   1090 
   1091 	num=delta->num;
   1092 	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
   1093 		if (strcmp(num, next->delta->num) == 0) {
   1094 			if (
   1095 				strcmp(getcaller(),next->login) != 0
   1096 			    &&	!sendmail(num, next->login)
   1097 			) {
   1098 			    rcserror("revision %s still locked by %s",
   1099 				num, next->login
   1100 			    );
   1101 			    return false;
   1102 			}
   1103 			diagnose("%s unlocked\n", next->delta->num);
   1104 			*trail = next->nextlock;
   1105 			next->delta->lockedby = 0;
   1106 			return true;
   1107                 }
   1108 	rcserror("no lock set on revision %s", num);
   1109 	return false;
   1110 }
   1111 
   1112 
   1113 
   1114 	static struct hshentry *
   1115 searchcutpt(object, length, store)
   1116 	char const *object;
   1117 	int length;
   1118 	struct hshentries *store;
   1119 /*   Function:  Search store and return entry with number being object. */
   1120 /*		cuttail = 0, if the entry is Head; otherwise, cuttail   */
   1121 /*              is the entry point to the one with number being object  */
   1122 
   1123 {
   1124 	cuthead = 0;
   1125 	while (compartial(store->first->num, object, length)) {
   1126 		cuthead = store->first;
   1127 		store = store->rest;
   1128 	}
   1129 	return store->first;
   1130 }
   1131 
   1132 
   1133 
   1134 	static int
   1135 branchpoint(strt, tail)
   1136 struct  hshentry        *strt,  *tail;
   1137 /*   Function: check whether the deltas between strt and tail	*/
   1138 /*		are locked or branch point, return 1 if any is  */
   1139 /*		locked or branch point; otherwise, return 0 and */
   1140 /*		mark deleted					*/
   1141 
   1142 {
   1143         struct  hshentry    *pt;
   1144 	struct rcslock const *lockpt;
   1145 
   1146 	for (pt = strt;  pt != tail;  pt = pt->next) {
   1147             if ( pt->branches ){ /*  a branch point  */
   1148 		rcserror("can't remove branch point %s", pt->num);
   1149 		return true;
   1150             }
   1151 	    for (lockpt = Locks;  lockpt;  lockpt = lockpt->nextlock)
   1152 		if (lockpt->delta == pt) {
   1153 		    rcserror("can't remove locked revision %s", pt->num);
   1154 		    return true;
   1155 		}
   1156 	    pt->selector = false;
   1157 	    diagnose("deleting revision %s\n",pt->num);
   1158         }
   1159 	return false;
   1160 }
   1161 
   1162 
   1163 
   1164 	static int
   1165 removerevs()
   1166 /*   Function:  get the revision range to be removed, and place the     */
   1167 /*              first revision removed in delstrt, the revision before  */
   1168 /*		delstrt in cuthead (0, if delstrt is head), and the	*/
   1169 /*		revision after the last removed revision in cuttail (0	*/
   1170 /*              if the last is a leaf                                   */
   1171 
   1172 {
   1173 	struct  hshentry *target, *target2, *temp;
   1174 	int length;
   1175 	int cmp;
   1176 
   1177 	if (!expandsym(delrev.strt, &numrev)) return 0;
   1178 	target = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
   1179         if ( ! target ) return 0;
   1180 	cmp = cmpnum(target->num, numrev.string);
   1181 	length = countnumflds(numrev.string);
   1182 
   1183 	if (delrev.code == 0) {  /*  -o  rev    or    -o  branch   */
   1184 	    if (length & 1)
   1185 		temp=searchcutpt(target->num,length+1,gendeltas);
   1186 	    else if (cmp) {
   1187 		rcserror("Revision %s doesn't exist.", numrev.string);
   1188 		return 0;
   1189 	    }
   1190 	    else
   1191 		temp = searchcutpt(numrev.string, length, gendeltas);
   1192 	    cuttail = target->next;
   1193             if ( branchpoint(temp, cuttail) ) {
   1194 		cuttail = 0;
   1195                 return 0;
   1196             }
   1197             delstrt = temp;     /* first revision to be removed   */
   1198             return 1;
   1199         }
   1200 
   1201 	if (length & 1) {   /*  invalid branch after -o   */
   1202 	    rcserror("invalid branch range %s after -o", numrev.string);
   1203             return 0;
   1204         }
   1205 
   1206 	if (delrev.code == 1) {  /*  -o  -rev   */
   1207             if ( length > 2 ) {
   1208                 temp = searchcutpt( target->num, length-1, gendeltas);
   1209                 cuttail = target->next;
   1210             }
   1211             else {
   1212                 temp = searchcutpt(target->num, length, gendeltas);
   1213                 cuttail = target;
   1214                 while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
   1215                     cuttail = cuttail->next;
   1216             }
   1217             if ( branchpoint(temp, cuttail) ){
   1218 		cuttail = 0;
   1219                 return 0;
   1220             }
   1221             delstrt = temp;
   1222             return 1;
   1223         }
   1224 
   1225 	if (delrev.code == 2) {   /*  -o  rev-   */
   1226             if ( length == 2 ) {
   1227                 temp = searchcutpt(target->num, 1,gendeltas);
   1228 		if (cmp)
   1229                     cuttail = target;
   1230                 else
   1231                     cuttail = target->next;
   1232             }
   1233             else  {
   1234 		if (cmp) {
   1235                     cuthead = target;
   1236                     if ( !(temp = target->next) ) return 0;
   1237                 }
   1238                 else
   1239                     temp = searchcutpt(target->num, length, gendeltas);
   1240 		getbranchno(temp->num, &numrev);  /* get branch number */
   1241 		VOID genrevs(numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas);
   1242             }
   1243             if ( branchpoint( temp, cuttail ) ) {
   1244 		cuttail = 0;
   1245                 return 0;
   1246             }
   1247             delstrt = temp;
   1248             return 1;
   1249         }
   1250 
   1251         /*   -o   rev1-rev2   */
   1252 	if (!expandsym(delrev.end, &numrev)) return 0;
   1253 	if (
   1254 		length != countnumflds(numrev.string)
   1255 	    ||	(length>2 && compartial(numrev.string, target->num, length-1))
   1256 	) {
   1257 	    rcserror("invalid revision range %s-%s",
   1258 		target->num, numrev.string
   1259 	    );
   1260             return 0;
   1261         }
   1262 
   1263 	target2 = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas);
   1264         if ( ! target2 ) return 0;
   1265 
   1266         if ( length > 2) {  /* delete revisions on branches  */
   1267             if ( cmpnum(target->num, target2->num) > 0) {
   1268 		cmp = cmpnum(target2->num, numrev.string);
   1269                 temp = target;
   1270                 target = target2;
   1271                 target2 = temp;
   1272             }
   1273 	    if (cmp) {
   1274                 if ( ! cmpnum(target->num, target2->num) ) {
   1275 		    rcserror("Revisions %s-%s don't exist.",
   1276 			delrev.strt, delrev.end
   1277 		    );
   1278                     return 0;
   1279                 }
   1280                 cuthead = target;
   1281                 temp = target->next;
   1282             }
   1283             else
   1284                 temp = searchcutpt(target->num, length, gendeltas);
   1285             cuttail = target2->next;
   1286         }
   1287         else { /*  delete revisions on trunk  */
   1288             if ( cmpnum( target->num, target2->num) < 0 ) {
   1289                 temp = target;
   1290                 target = target2;
   1291                 target2 = temp;
   1292             }
   1293             else
   1294 		cmp = cmpnum(target2->num, numrev.string);
   1295 	    if (cmp) {
   1296                 if ( ! cmpnum(target->num, target2->num) ) {
   1297 		    rcserror("Revisions %s-%s don't exist.",
   1298 			delrev.strt, delrev.end
   1299 		    );
   1300                     return 0;
   1301                 }
   1302                 cuttail = target2;
   1303             }
   1304             else
   1305                 cuttail = target2->next;
   1306             temp = searchcutpt(target->num, length, gendeltas);
   1307         }
   1308         if ( branchpoint(temp, cuttail) )  {
   1309 	    cuttail = 0;
   1310             return 0;
   1311         }
   1312         delstrt = temp;
   1313         return 1;
   1314 }
   1315 
   1316 
   1317 
   1318 	static int
   1319 doassoc()
   1320 /* Add or delete (if !revno) association that is stored in assoclst.  */
   1321 {
   1322 	char const *p;
   1323 	int changed = false;
   1324 	struct Symrev const *curassoc;
   1325 	struct assoc **pre, *pt;
   1326 
   1327         /*  add new associations   */
   1328 	for (curassoc = assoclst;  curassoc;  curassoc = curassoc->nextsym) {
   1329 	    char const *ssymbol = curassoc->ssymbol;
   1330 
   1331 	    if (!curassoc->revno) {  /* delete symbol  */
   1332 		for (pre = &Symbols;  ;  pre = &pt->nextassoc)
   1333 		    if (!(pt = *pre)) {
   1334 			rcswarn("can't delete nonexisting symbol %s", ssymbol);
   1335 			break;
   1336 		    } else if (strcmp(pt->symbol, ssymbol) == 0) {
   1337 			*pre = pt->nextassoc;
   1338 			changed = true;
   1339 			break;
   1340 		    }
   1341 	    }
   1342 	    else {
   1343 		if (curassoc->revno[0]) {
   1344 		    p = 0;
   1345 		    if (expandsym(curassoc->revno, &numrev))
   1346 			p = fstr_save(numrev.string);
   1347 		} else if (!(p = tiprev()))
   1348 		    rcserror("no latest revision to associate with symbol %s",
   1349 			    ssymbol
   1350 		    );
   1351 		if (p)
   1352 		    changed |= addsymbol(p, ssymbol, curassoc->override);
   1353 	    }
   1354         }
   1355 	return changed;
   1356 }
   1357 
   1358 
   1359 
   1360 	static int
   1361 dolocks()
   1362 /* Function: remove lock for caller or first lock if unlockcaller is set;
   1363  *           remove locks which are stored in rmvlocklst,
   1364  *           add new locks which are stored in newlocklst,
   1365  *           add lock for Dbranch or Head if lockhead is set.
   1366  */
   1367 {
   1368 	struct Lockrev const *lockpt;
   1369 	struct hshentry *target;
   1370 	int changed = false;
   1371 
   1372 	if (unlockcaller) { /*  find lock for caller  */
   1373             if ( Head ) {
   1374 		if (Locks) {
   1375 		    switch (findlock(true, &target)) {
   1376 		      case 0:
   1377 			/* remove most recent lock */
   1378 			changed |= breaklock(Locks->delta);
   1379 			break;
   1380 		      case 1:
   1381 			diagnose("%s unlocked\n",target->num);
   1382 			changed = true;
   1383 			break;
   1384 		    }
   1385 		} else {
   1386 		    rcswarn("No locks are set.");
   1387 		}
   1388             } else {
   1389 		rcswarn("can't unlock an empty tree");
   1390             }
   1391         }
   1392 
   1393         /*  remove locks which are stored in rmvlocklst   */
   1394 	for (lockpt = rmvlocklst;  lockpt;  lockpt = lockpt->nextrev)
   1395 	    if (expandsym(lockpt->revno, &numrev)) {
   1396 		target = genrevs(numrev.string, (char *)0, (char *)0, (char *)0, &gendeltas);
   1397                 if ( target ) {
   1398 		   if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
   1399 			rcserror("can't unlock nonexisting revision %s",
   1400 				lockpt->revno
   1401 			);
   1402                    else
   1403 			changed |= breaklock(target);
   1404                         /* breaklock does its own diagnose */
   1405                 }
   1406             }
   1407 
   1408         /*  add new locks which stored in newlocklst  */
   1409 	for (lockpt = newlocklst;  lockpt;  lockpt = lockpt->nextrev)
   1410 	    changed |= setlock(lockpt->revno);
   1411 
   1412 	if (lockhead) { /*  lock default branch or head  */
   1413 	    if (Dbranch)
   1414 		changed |= setlock(Dbranch);
   1415 	    else if (Head)
   1416 		changed |= setlock(Head->num);
   1417 	    else
   1418 		rcswarn("can't lock an empty tree");
   1419 	}
   1420 	return changed;
   1421 }
   1422 
   1423 
   1424 
   1425 	static int
   1426 setlock(rev)
   1427 	char const *rev;
   1428 /* Function: Given a revision or branch number, finds the corresponding
   1429  * delta and locks it for caller.
   1430  */
   1431 {
   1432         struct  hshentry *target;
   1433 	int r;
   1434 
   1435 	if (expandsym(rev, &numrev)) {
   1436 	    target = genrevs(numrev.string, (char*)0, (char*)0,
   1437 			     (char*)0, &gendeltas);
   1438             if ( target ) {
   1439 	       if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
   1440 		    rcserror("can't lock nonexisting revision %s",
   1441 			numrev.string
   1442 		    );
   1443 	       else {
   1444 		    if ((r = addlock(target, false)) < 0  &&  breaklock(target))
   1445 			r = addlock(target, true);
   1446 		    if (0 <= r) {
   1447 			if (r)
   1448 			    diagnose("%s locked\n", target->num);
   1449 			return r;
   1450 		    }
   1451 	       }
   1452 	    }
   1453 	}
   1454 	return 0;
   1455 }
   1456 
   1457 
   1458 	static int
   1459 domessages()
   1460 {
   1461 	struct hshentry *target;
   1462 	struct Message *p;
   1463 	int changed = false;
   1464 
   1465 	for (p = messagelst;  p;  p = p->nextmessage)
   1466 	    if (
   1467 		expandsym(p->revno, &numrev)  &&
   1468 		(target = genrevs(
   1469 			numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas
   1470 		))
   1471 	    ) {
   1472 		/*
   1473 		 * We can't check the old log -- it's much later in the file.
   1474 		 * We pessimistically assume that it changed.
   1475 		 */
   1476 		target->log = p->message;
   1477 		changed = true;
   1478 	    }
   1479 	return changed;
   1480 }
   1481 
   1482 
   1483 	static int
   1484 rcs_setstate(rev,status)
   1485 	char const *rev, *status;
   1486 /* Function: Given a revision or branch number, finds the corresponding delta
   1487  * and sets its state to status.
   1488  */
   1489 {
   1490         struct  hshentry *target;
   1491 
   1492 	if (expandsym(rev, &numrev)) {
   1493 	    target = genrevs(numrev.string, (char*)0, (char*)0,
   1494 			     (char*)0, &gendeltas);
   1495             if ( target ) {
   1496 	       if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
   1497 		    rcserror("can't set state of nonexisting revision %s",
   1498 			numrev.string
   1499 		    );
   1500 	       else if (strcmp(target->state, status) != 0) {
   1501                     target->state = status;
   1502 		    return true;
   1503 	       }
   1504 	    }
   1505 	}
   1506 	return false;
   1507 }
   1508 
   1509 
   1510 
   1511 
   1512 
   1513 	static int
   1514 buildeltatext(deltas)
   1515 	struct hshentries const *deltas;
   1516 /*   Function:  put the delta text on frewrite and make necessary   */
   1517 /*              change to delta text                                */
   1518 {
   1519 	register FILE *fcut;	/* temporary file to rebuild delta tree */
   1520 	char const *cutname;
   1521 
   1522 	fcut = 0;
   1523 	cuttail->selector = false;
   1524 	scanlogtext(deltas->first, false);
   1525         if ( cuthead )  {
   1526 	    cutname = maketemp(3);
   1527 	    if (!(fcut = fopenSafer(cutname, FOPEN_WPLUS_WORK))) {
   1528 		efaterror(cutname);
   1529             }
   1530 
   1531 	    while (deltas->first != cuthead) {
   1532 		deltas = deltas->rest;
   1533 		scanlogtext(deltas->first, true);
   1534             }
   1535 
   1536 	    snapshotedit(fcut);
   1537 	    Orewind(fcut);
   1538 	    aflush(fcut);
   1539         }
   1540 
   1541 	while (deltas->first != cuttail)
   1542 	    scanlogtext((deltas = deltas->rest)->first, true);
   1543 	finishedit((struct hshentry*)0, (FILE*)0, true);
   1544 	Ozclose(&fcopy);
   1545 
   1546 	if (fcut) {
   1547 	    char const *diffname = maketemp(0);
   1548 	    char const *diffv[6 + !!OPEN_O_BINARY];
   1549 	    char const **diffp = diffv;
   1550 	    *++diffp = DIFF;
   1551 	    *++diffp = DIFFFLAGS;
   1552 #	    if OPEN_O_BINARY
   1553 		if (Expand == BINARY_EXPAND)
   1554 		    *++diffp == "--binary";
   1555 #	    endif
   1556 	    *++diffp = "-";
   1557 	    *++diffp = resultname;
   1558 	    *++diffp = 0;
   1559 	    switch (runv(fileno(fcut), diffname, diffv)) {
   1560 		case DIFF_FAILURE: case DIFF_SUCCESS: break;
   1561 		default: rcsfaterror("diff failed");
   1562 	    }
   1563 	    Ofclose(fcut);
   1564 	    return putdtext(cuttail,diffname,frewrite,true);
   1565 	} else
   1566 	    return putdtext(cuttail,resultname,frewrite,false);
   1567 }
   1568 
   1569 
   1570 
   1571 	static void
   1572 buildtree()
   1573 /*   Function:  actually removes revisions whose selector field  */
   1574 /*		is false, and rebuilds the linkage of deltas.	 */
   1575 /*              asks for reconfirmation if deleting last revision*/
   1576 {
   1577 	struct	hshentry   * Delta;
   1578         struct  branchhead      *pt, *pre;
   1579 
   1580         if ( cuthead )
   1581            if ( cuthead->next == delstrt )
   1582                 cuthead->next = cuttail;
   1583            else {
   1584                 pre = pt = cuthead->branches;
   1585                 while( pt && pt->hsh != delstrt )  {
   1586                     pre = pt;
   1587                     pt = pt->nextbranch;
   1588                 }
   1589                 if ( cuttail && pt )
   1590                     pt->hsh = cuttail;
   1591                 else if ( pt == pre )
   1592                     cuthead->branches = pt->nextbranch;
   1593                 else
   1594                     pre->nextbranch = pt->nextbranch;
   1595             }
   1596 	else {
   1597 	    if (!cuttail && !quietflag) {
   1598 		if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) {
   1599 		    rcserror("No revision deleted");
   1600 		    Delta = delstrt;
   1601 		    while( Delta) {
   1602 			Delta->selector = true;
   1603 			Delta = Delta->next;
   1604 		    }
   1605 		    return;
   1606 		}
   1607 	    }
   1608             Head = cuttail;
   1609 	}
   1610         return;
   1611 }
   1612 
   1613 #if RCS_lint
   1614 /* This lets us lint everything all at once. */
   1615 
   1616 char const cmdid[] = "";
   1617 
   1618 #define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();}
   1619 
   1620 	int
   1621 main(argc, argv)
   1622 	int argc;
   1623 	char **argv;
   1624 {
   1625 	go(ciId,	ciExit);
   1626 	go(coId,	coExit);
   1627 	go(identId,	identExit);
   1628 	go(mergeId,	mergeExit);
   1629 	go(rcsId,	exiterr);
   1630 	go(rcscleanId,	rcscleanExit);
   1631 	go(rcsdiffId,	rdiffExit);
   1632 	go(rcsmergeId,	rmergeExit);
   1633 	go(rlogId,	rlogExit);
   1634 	return 0;
   1635 }
   1636 #endif
   1637