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