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