150476Speter/* Change RCS file attributes.  */
247857Sbrian
347857Sbrian/* Copyright 1982, 1988, 1989 Walter Tichy
447857Sbrian   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
547857Sbrian   Distributed under license by the Free Software Foundation, Inc.
647857Sbrian
747857SbrianThis file is part of RCS.
847857Sbrian
947857SbrianRCS is free software; you can redistribute it and/or modify
1047857Sbrianit under the terms of the GNU General Public License as published by
1147857Sbrianthe Free Software Foundation; either version 2, or (at your option)
1247857Sbrianany later version.
1347857Sbrian
1447857SbrianRCS is distributed in the hope that it will be useful,
1547857Sbrianbut WITHOUT ANY WARRANTY; without even the implied warranty of
1647857SbrianMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1747857SbrianGNU General Public License for more details.
1847857Sbrian
1947857SbrianYou should have received a copy of the GNU General Public License
2047857Sbrianalong with RCS; see the file COPYING.
2147857SbrianIf not, write to the Free Software Foundation,
2247857Sbrian59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
2347857Sbrian
2447857SbrianReport problems and direct all questions to:
2547857Sbrian
2647857Sbrian    rcs-bugs@cs.purdue.edu
2747857Sbrian
2847857Sbrian*/
2947857Sbrian
3047857Sbrian/*
3147857Sbrian * Revision 5.21  1995/06/16 06:19:24  eggert
3247857Sbrian * Update FSF address.
3347857Sbrian *
3447857Sbrian * Revision 5.20  1995/06/01 16:23:43  eggert
3547857Sbrian * (main): Warn if no options were given.  Punctuate messages properly.
3647857Sbrian *
3747857Sbrian * (sendmail): Rewind mailmess before flushing it.
3847857Sbrian * Output another warning if mail should work but fails.
3947857Sbrian *
4047857Sbrian * (buildeltatext): Pass "--binary" if -kb and if --binary makes a difference.
4147857Sbrian *
4247857Sbrian * Revision 5.19  1994/03/17 14:05:48  eggert
4347857Sbrian * Use ORCSerror to clean up after a fatal error.  Remove lint.
4447857Sbrian * Specify subprocess input via file descriptor, not file name.  Remove lint.
4547857Sbrian * Flush stderr after prompt.
4647857Sbrian *
4747857Sbrian * Revision 5.18  1993/11/09 17:40:15  eggert
4847857Sbrian * -V now prints version on stdout and exits.  Don't print usage twice.
4947857Sbrian *
5047857Sbrian * Revision 5.17  1993/11/03 17:42:27  eggert
5147857Sbrian * Add -z.  Don't lose track of -m or -t when there are no other changes.
5247857Sbrian * Don't discard ignored phrases.  Improve quality of diagnostics.
5347857Sbrian *
5447857Sbrian * Revision 5.16  1992/07/28  16:12:44  eggert
5547857Sbrian * rcs -l now asks whether you want to break the lock.
5647857Sbrian * Add -V.  Set RCS file's mode and time at right moment.
5747857Sbrian *
5847857Sbrian * Revision 5.15  1992/02/17  23:02:20  eggert
5947857Sbrian * Add -T.
6047857Sbrian *
6147857Sbrian * Revision 5.14  1992/01/27  16:42:53  eggert
6247857Sbrian * Add -M.  Avoid invoking umask(); it's one less thing to configure.
6347857Sbrian * Add support for bad_creat0.  lint -> RCS_lint
6447857Sbrian *
6547857Sbrian * Revision 5.13  1992/01/06  02:42:34  eggert
6647857Sbrian * Avoid changing RCS file in common cases where no change can occur.
6747857Sbrian *
6847857Sbrian * Revision 5.12  1991/11/20  17:58:08  eggert
6947857Sbrian * Don't read the delta tree from a nonexistent RCS file.
7047857Sbrian *
7147857Sbrian * Revision 5.11  1991/10/07  17:32:46  eggert
7247857Sbrian * Remove lint.
7347857Sbrian *
7447857Sbrian * Revision 5.10  1991/08/19  23:17:54  eggert
7547857Sbrian * Add -m, -r$, piece tables.  Revision separator is `:', not `-'.  Tune.
7647857Sbrian *
7747857Sbrian * Revision 5.9  1991/04/21  11:58:18  eggert
7847857Sbrian * Add -x, RCSINIT, MS-DOS support.
7947857Sbrian *
8047857Sbrian * Revision 5.8  1991/02/25  07:12:38  eggert
8147857Sbrian * strsave -> str_save (DG/UX name clash)
8247857Sbrian * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability
8347857Sbrian *
8447857Sbrian * Revision 5.7  1990/12/18  17:19:21  eggert
8547857Sbrian * Fix bug with multiple -n and -N options.
8647857Sbrian *
8747857Sbrian * Revision 5.6  1990/12/04  05:18:40  eggert
8847857Sbrian * Use -I for prompts and -q for diagnostics.
8947857Sbrian *
9047857Sbrian * Revision 5.5  1990/11/11  00:06:35  eggert
9147857Sbrian * Fix `rcs -e' core dump.
9247857Sbrian *
9347857Sbrian * Revision 5.4  1990/11/01  05:03:33  eggert
9447857Sbrian * Add -I and new -t behavior.  Permit arbitrary data in logs.
9547857Sbrian *
9647857Sbrian * Revision 5.3  1990/10/04  06:30:16  eggert
9747857Sbrian * Accumulate exit status across files.
9847857Sbrian *
9947857Sbrian * Revision 5.2  1990/09/04  08:02:17  eggert
10047857Sbrian * Standardize yes-or-no procedure.
10147857Sbrian *
10247857Sbrian * Revision 5.1  1990/08/29  07:13:51  eggert
10347857Sbrian * Remove unused setuid support.  Clean old log messages too.
10447857Sbrian *
10547857Sbrian * Revision 5.0  1990/08/22  08:12:42  eggert
10647857Sbrian * Don't lose names when applying -a option to multiple files.
107244040Seadler * Remove compile-time limits; use malloc instead.  Add setuid support.
10847857Sbrian * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
10947857Sbrian * Ansify and Posixate.  Add -V.  Fix umask bug.  Make linting easier.  Tune.
11047857Sbrian * Yield proper exit status.  Check diff's output.
11147857Sbrian *
11247857Sbrian * Revision 4.11  89/05/01  15:12:06  narten
113138815Sbrian * changed copyright header to reflect current distribution rules
11447857Sbrian *
11547857Sbrian * Revision 4.10  88/11/08  16:01:54  narten
11647857Sbrian * didn't install previous patch correctly
11747857Sbrian *
118113346Skeramida * Revision 4.9  88/11/08  13:56:01  narten
119113346Skeramida * removed include <sysexits.h> (not needed)
12047857Sbrian * minor fix for -A option
12147857Sbrian *
12247857Sbrian * Revision 4.8  88/08/09  19:12:27  eggert
12347857Sbrian * Don't access freed storage.
124113346Skeramida * Use execv(), not system(); yield proper exit status; remove lint.
125113346Skeramida *
12647857Sbrian * Revision 4.7  87/12/18  11:37:17  narten
12747857Sbrian * lint cleanups (Guy Harris)
12847857Sbrian *
12947857Sbrian * Revision 4.6  87/10/18  10:28:48  narten
130113346Skeramida * Updating verison numbers. Changes relative to 1.1 are actually
131113346Skeramida * relative to 4.3
13247857Sbrian *
13347857Sbrian * Revision 1.4  87/09/24  13:58:52  narten
13447857Sbrian * Sources now pass through lint (if you ignore printf/sprintf/fprintf
13547857Sbrian * warnings)
13647857Sbrian *
13747857Sbrian * Revision 1.3  87/03/27  14:21:55  jenkins
13847857Sbrian * Port to suns
13947857Sbrian *
14047857Sbrian * Revision 1.2  85/12/17  13:59:09  albitz
14147857Sbrian * Changed setstate to rcs_setstate because of conflict with random.o.
14247857Sbrian *
14347857Sbrian * Revision 4.3  83/12/15  12:27:33  wft
14447857Sbrian * rcs -u now breaks most recent lock if it can't find a lock by the caller.
14547857Sbrian *
14664783Sbrian * Revision 4.2  83/12/05  10:18:20  wft
14751049Sbrian * Added conditional compilation for sending mail.
148113346Skeramida * Alternatives: V4_2BSD, V6, USG, and other.
149113346Skeramida *
15047857Sbrian * Revision 4.1  83/05/10  16:43:02  wft
15147857Sbrian * Simplified breaklock(); added calls to findlock() and getcaller().
15247857Sbrian * Added option -b (default branch). Updated -s and -w for -b.
15347857Sbrian * Removed calls to stat(); now done by pairfilenames().
15447857Sbrian * Replaced most catchints() calls with restoreints().
15547857Sbrian * Removed check for exit status of delivermail().
15647857Sbrian * Directed all interactive output to stderr.
15747857Sbrian *
15847857Sbrian * Revision 3.9.1.1  83/12/02  22:08:51  wft
15947857Sbrian * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
16047857Sbrian *
16147857Sbrian * Revision 3.9  83/02/15  15:38:39  wft
16247857Sbrian * Added call to fastcopy() to copy remainder of RCS file.
16347857Sbrian *
16447857Sbrian * Revision 3.8  83/01/18  17:37:51  wft
16547857Sbrian * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
16647857Sbrian *
16747857Sbrian * Revision 3.7  83/01/15  18:04:25  wft
16847857Sbrian * Removed putree(); replaced with puttree() in rcssyn.c.
16947857Sbrian * Combined putdellog() and scanlogtext(); deleted putdellog().
17047857Sbrian * Cleaned up diagnostics and error messages. Fixed problem with
17147857Sbrian * mutilated files in case of deletions in 2 files in a single command.
17247857Sbrian * Changed marking of selector from 'D' to DELETE.
17347857Sbrian *
17447857Sbrian * Revision 3.6  83/01/14  15:37:31  wft
17547857Sbrian * Added ignoring of interrupts while new RCS file is renamed;
17647857Sbrian * Avoids deletion of RCS files by interrupts.
17747857Sbrian *
17847857Sbrian * Revision 3.5  82/12/10  21:11:39  wft
17947857Sbrian * Removed unused variables, fixed checking of return code from diff,
18047857Sbrian * introduced variant COMPAT2 for skipping Suffix on -A files.
18147857Sbrian *
18247857Sbrian * Revision 3.4  82/12/04  13:18:20  wft
18347857Sbrian * Replaced getdelta() with gettree(), changed breaklock to update
18447857Sbrian * field lockedby, added some diagnostics.
18551049Sbrian *
18647857Sbrian * Revision 3.3  82/12/03  17:08:04  wft
18747857Sbrian * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
18847857Sbrian * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
18947857Sbrian * fixed -u for missing revno. Disambiguated structure members.
19047857Sbrian *
19164783Sbrian * Revision 3.2  82/10/18  21:05:07  wft
19247857Sbrian * rcs -i now generates a file mode given by the umask minus write permission;
19347857Sbrian * otherwise, rcs keeps the mode, but removes write permission.
19447857Sbrian * 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