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