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