xref: /dragonfly/gnu/usr.bin/rcs/rcs/rcs.c (revision 37de577a)
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  * $FreeBSD: src/gnu/usr.bin/rcs/rcs/rcs.c,v 1.7 1999/08/27 23:36:52 peter Exp $
32  * $DragonFly: src/gnu/usr.bin/rcs/rcs/rcs.c,v 1.2 2003/06/17 04:25:47 dillon Exp $
33  *
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", "$DragonFly: src/gnu/usr.bin/rcs/rcs/rcs.c,v 1.2 2003/06/17 04:25:47 dillon 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         register int c;
734 	register 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         register 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 )
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