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