/*
 *                      RCS create/change operation
 */
#ifndef lint
static char rcsid[]=
"$Header: /usr/src/local/bin/rcs/src/RCS/rcs.c,v 4.11 89/05/01 15:12:06 narten Exp $ Purdue CS";
#endif
/* Copyright (C) 1982, 1988, 1989 Walter Tichy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Walter Tichy.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Report all problems and direct all questions to:
 *   rcs-bugs@cs.purdue.edu
 * 







*/




/* $Log:	rcs.c,v $
 * Revision 4.11  89/05/01  15:12:06  narten
 * changed copyright header to reflect current distribution rules
 * 
 * Revision 4.10  88/11/08  16:01:54  narten
 * didn't install previous patch correctly
 * 
 * Revision 4.9  88/11/08  13:56:01  narten
 * removed include <sysexits.h> (not needed)
 * minor fix for -A option
 * 
 * Revision 4.8  88/11/08  12:01:58  narten
 * changes from  eggert@sm.unisys.com (Paul Eggert)
 * 
 * Revision 4.8  88/08/09  19:12:27  eggert
 * Don't access freed storage.
 * Use execv(), not system(); yield proper exit status; remove lint.
 * 
 * Revision 4.7  87/12/18  11:37:17  narten
 * lint cleanups (Guy Harris)
 * 
 * Revision 4.6  87/10/18  10:28:48  narten
 * Updating verison numbers. Changes relative to 1.1 are actually 
 * relative to 4.3
 * 
 * Revision 1.4  87/09/24  13:58:52  narten
 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
 * warnings)
 * 
 * Revision 1.3  87/03/27  14:21:55  jenkins
 * Port to suns
 * 
 * Revision 1.2  85/12/17  13:59:09  albitz
 * Changed setstate to rcs_setstate because of conflict with random.o.
 * 
 * Revision 1.1  84/01/23  14:50:09  kcs
 * Initial revision
 * 
 * Revision 4.3  83/12/15  12:27:33  wft
 * rcs -u now breaks most recent lock if it can't find a lock by the caller.
 * 
 * Revision 4.2  83/12/05  10:18:20  wft
 * Added conditional compilation for sending mail.
 * Alternatives: V4_2BSD, V6, USG, and other.
 * 
 * Revision 4.1  83/05/10  16:43:02  wft
 * Simplified breaklock(); added calls to findlock() and getcaller().
 * Added option -b (default branch). Updated -s and -w for -b.
 * Removed calls to stat(); now done by pairfilenames().
 * Replaced most catchints() calls with restoreints().
 * Removed check for exit status of delivermail().
 * Directed all interactive output to stderr.
 * 
 * Revision 3.9.1.1  83/12/02  22:08:51  wft
 * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
 * 
 * Revision 3.9  83/02/15  15:38:39  wft
 * Added call to fastcopy() to copy remainder of RCS file.
 *
 * Revision 3.8  83/01/18  17:37:51  wft
 * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
 *
 * Revision 3.7  83/01/15  18:04:25  wft
 * Removed putree(); replaced with puttree() in rcssyn.c.
 * Combined putdellog() and scanlogtext(); deleted putdellog().
 * Cleaned up diagnostics and error messages. Fixed problem with
 * mutilated files in case of deletions in 2 files in a single command.
 * Changed marking of selector from 'D' to DELETE.
 *
 * Revision 3.6  83/01/14  15:37:31  wft
 * Added ignoring of interrupts while new RCS file is renamed;
 * Avoids deletion of RCS files by interrupts.
 *
 * Revision 3.5  82/12/10  21:11:39  wft
 * Removed unused variables, fixed checking of return code from diff,
 * introduced variant COMPAT2 for skipping Suffix on -A files.
 *
 * Revision 3.4  82/12/04  13:18:20  wft
 * Replaced getdelta() with gettree(), changed breaklock to update
 * field lockedby, added some diagnostics.
 *
 * Revision 3.3  82/12/03  17:08:04  wft
 * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
 * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
 * fixed -u for missing revno. Disambiguated structure members.
 *
 * Revision 3.2  82/10/18  21:05:07  wft
 * rcs -i now generates a file mode given by the umask minus write permission;
 * otherwise, rcs keeps the mode, but removes write permission.
 * I added a check for write error, fixed call to getlogin(), replaced
 * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
 * conflicting, long identifiers.
 *
 * Revision 3.1  82/10/13  16:11:07  wft
 * fixed type of variables receiving from getc() (char -> int).
 */


#include <sys/types.h>
#include <sys/stat.h>
#include "rcsbase.h"
#include <paths.h>
#ifndef lint
static char rcsbaseid[] = RCSBASE;
#endif

extern FILE * fopen();
extern char * bindex();
extern int  expandsym();                /* get numeric revision name        */
extern struct  hshentry  * getnum();
extern struct  lock      * addlock();   /* add a lock                       */
extern char              * getid();
extern char              Klog[], Khead[], Kaccess[], Kbranch[], Ktext[];
#ifdef COMPAT2
extern char Ksuffix[];
#endif
extern char * getcaller();              /* get login of caller              */
extern struct hshentry   * findlock();  /* find and remove lock             */
extern struct hshentry   * genrevs();
extern char * checkid();                /* check an identifier              */
extern char * getfullRCSname();         /* get full path name of RCS file   */
extern char * mktempfile();             /* temporary file name generator    */
extern free();
extern void catchints();
extern void ignoreints();
extern int  nerror;                     /* counter for errors               */
extern int  quietflag;                  /* diagnoses suppressed if true     */
extern char curlogmsg[];                /* current log message              */
extern char * resultfile;               /* filename for fcopy		    */
extern FILE *fcopy;                     /* result file during editing       */
extern FILE * finptr;                   /* RCS input file                   */
extern FILE * frewrite;                 /* new RCS file                     */
extern int    rewriteflag;              /* indicates whether input should be*/
					/* echoed to frewrite               */

char * newRCSfilename, * diffilename, * cutfilename;
char * RCSfilename, * workfilename;
extern struct stat RCSstat, workstat; /* file status of RCS and work file   */
extern int  haveRCSstat, haveworkstat;/* status indicators                  */

char accessorlst[strtsize];
FILE * fcut;        /* temporary file to rebuild delta tree                 */
int    oldumask;    /* save umask                                           */

int initflag, strictlock, strict_selected, textflag;
char * textfile, * accessfile;
char * caller, numrev[30];            /* caller's login;               */
struct  access  * newaccessor,  * rmvaccessor,  * rplaccessor;
struct  access  *curaccess,  *rmaccess;
struct  hshentry        * gendeltas[hshsize];

struct  Lockrev {
        char    * revno;
        struct  Lockrev   * nextrev;
};

struct  Symrev {
        char    * revno;
        char    * ssymbol;
        int     override;
        struct  Symrev  * nextsym;
};

struct  Status {
        char    * revno;
        char    * status;
        struct  Status  * nextstatus;
};

struct delrevpair {
        char    * strt;
        char    * end;
        int     code;
};

struct  Lockrev * newlocklst,   * rmvlocklst;
struct  Symrev  * assoclst,  * lastassoc;
struct  Status  * statelst,  * laststate;
struct  delrevpair      * delrev;
struct  hshentry        * cuthead,  *cuttail,  * delstrt;
char    branchnum[revlength], * branchsym;
struct  hshentry branchdummy;
char	* commsyml;
char    * headstate;
int     lockhead,unlockcaller,chgheadstate,branchflag,commentflag;
int     delaccessflag;
enum    stringwork {copy, edit, empty}; /* expand and edit_expand not needed */


main (argc, argv)
int argc;
char * argv[];
{
        char    *comdusge;
        int     result;
	struct	access	*removeaccess(),  * getaccessor();
        struct  Lockrev *rmnewlocklst();
        struct  Lockrev *curlock,  * rmvlock, *lockpt;
        struct  Status  * curstate;
        struct  access    *temp, *temptr;
	int status;

	status = 0;
        nerror = 0;
	catchints();
        cmdid = "rcs";
        quietflag = false;
        comdusge ="command format:\nrcs -i -alogins -Alogins -e[logins] -b[rev] -c[commentleader] -l[rev] -u[rev] -L -U -nname[:rev] -Nname[:rev] -orange -sstate[:rev] -t[textfile] file....";
        rplaccessor = nil;     delstrt = nil;
        accessfile = textfile = caller = nil;
        branchflag = commentflag = chgheadstate = false;
        lockhead = false; unlockcaller=false;
        initflag= textflag = false;
        strict_selected = 0;

	caller=getcaller();
        laststate = statelst = nil;
        lastassoc = assoclst = nil;
        curlock = rmvlock = newlocklst = rmvlocklst = nil;
        curaccess = rmaccess = rmvaccessor = newaccessor = nil;
        delaccessflag = false;

        /*  preprocessing command options    */
        while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
                switch ((*argv)[1]) {

                case 'i':   /*  initail version  */
                        initflag = true;
                        break;

                case 'b':  /* change default branch */
                        if (branchflag)warn("Redfinition of option -b");
                        branchflag= true;
                        branchsym = (*argv)+2;
                        break;

                case 'c':   /*  change comment symbol   */
                        if (commentflag)warn("Redefinition of option -c");
                        commentflag = true;
                        commsyml = (*argv)+2;
                        break;

                case 'a':  /*  add new accessor   */
                        if ( (*argv)[2] == '\0') {
                            error("Login name missing after -a");
                        }
                        if ( (temp = getaccessor((*argv)+1)) ) {
                            if ( newaccessor )
                                curaccess->nextaccess = temp->nextaccess;
                            else
                                newaccessor = temp->nextaccess;
                            temp->nextaccess = nil;
                            curaccess = temp;
                        }
                        break;

                case 'A':  /*  append access list according to accessfile  */
                        if ( (*argv)[2] == '\0') {
                            error("Missing file name after -A");
                            break;
                        }
                        if ( accessfile) warn("Redefinition of option -A");
                        *argv = *argv+2;
                        if( pairfilenames(1, argv, true, false) > 0) {
                            releaselst(newaccessor);
                            newaccessor = curaccess = nil;
                            releaselst(rmvaccessor);
                            rmvaccessor = rmaccess = nil;
                            accessfile = RCSfilename;
                        }
                        else
                            accessfile = nil;
                        break;

                case 'e':    /*  remove accessors   */
                        if ( (*argv)[2] == '\0' ) {
                            delaccessflag = true;
                            break;
                        }
                        if ( (temp = getaccessor((*argv)+1))  ) {
                            if ( rmvaccessor )
                                rmaccess->nextaccess = temp->nextaccess;
                            else
                                rmvaccessor = temp->nextaccess;
                            temptr = temp->nextaccess;
                            temp->nextaccess = nil;
                            rmaccess = temp;
                            while( temptr ) {
                                newaccessor = removeaccess(temptr,newaccessor,false);
                                temptr = temptr->nextaccess;
                            }
                            curaccess = temp = newaccessor;
                            while( temp){
                                curaccess = temp;
                                temp = temp->nextaccess;
                            }
                        }
                        break;

                case 'l':    /*   lock a revision if it is unlocked   */
                        if ( (*argv)[2] == '\0'){ /* lock head or def. branch */
                            lockhead = true;
                            break;
                        }
                        lockpt = (struct Lockrev *)talloc(sizeof(struct Lockrev));
                        lockpt->revno = (*argv)+2;
                        lockpt->nextrev = nil;
                        if ( curlock )
                            curlock->nextrev = lockpt;
                        else
                            newlocklst = lockpt;
                        curlock = lockpt;
                        break;

                case 'u':   /*  release lock of a locked revision   */
                        if ( (*argv)[2] == '\0'){ /*  unlock head  */
                            unlockcaller=true;
                            break;
                        }
                        lockpt = (struct Lockrev *)talloc(sizeof(struct Lockrev));
                        lockpt->revno = (*argv)+2;
                        lockpt->nextrev = nil;
                        if (rmvlock)
                            rmvlock->nextrev = lockpt;
                        else
                            rmvlocklst = lockpt;
                        rmvlock = lockpt;

                        curlock = rmnewlocklst(lockpt);
                        break;

                case 'L':   /*  set strict locking */
                        if (strict_selected++) {  /* Already selected L or U? */
			   if (!strictlock)	  /* Already selected -U? */
			       warn("Option -L overrides -U");
                        }
                        strictlock = true;
                        break;

                case 'U':   /*  release strict locking */
                        if (strict_selected++) {  /* Already selected L or U? */
			   if (strictlock)	  /* Already selected -L? */
			       warn("Option -L overrides -U");
                        }
			else
			    strictlock = false;
                        break;

                case 'n':    /*  add new association: error, if name exists */
                        if ( (*argv)[2] == '\0') {
                            error("Missing symbolic name after -n");
                            break;
                        }
                        getassoclst(false, (*argv)+1);
                        break;

                case 'N':   /*  add or change association   */
                        if ( (*argv)[2] == '\0') {
                            error("Missing symbolic name after -N");
                            break;
                        }
                        getassoclst(true, (*argv)+1);
                        break;

                case 'o':   /*  delete revisins  */
                        if (delrev) warn("Redefinition of option -o");
                        if ( (*argv)[2] == '\0' ) {
                            error("Missing revision range after -o");
                            break;
                        }
                        getdelrev( (*argv)+1 );
                        break;

                case 's':   /*  change state attribute of a revision  */
                        if ( (*argv)[2] == '\0') {
                            error("State missing after -s");
                            break;
                        }
                        getstates( (*argv)+1);
                        break;

                case 't':   /*  change descriptive text   */
                        textflag=true;
                        if ((*argv)[2]!='\0'){
                                if (textfile!=nil)warn("Redefinition of -t option");
                                textfile = (*argv)+2;
                        }
                        break;

                case 'q':
                        quietflag = true;
                        break;
                default:
                        faterror("Unknown option: %s\n%s", *argv, comdusge);
                };
        }  /* end processing of options */

        if (argc<1) faterror("No input file\n%s", comdusge);
        if (nerror) {   /*  exit, if any error in command options  */
            diagnose("%s aborted",cmdid);
            exit(1);
        }
        if (accessfile) /*  get replacement for access list   */
            getrplaccess();
        if (nerror) {
            diagnose("%s aborted",cmdid);
            exit(1);
        }

        /* now handle all filenames */
        do {
        rewriteflag = false;
        finptr=frewrite=NULL;

        if ( initflag ) {
            switch( pairfilenames(argc, argv, false, false) ) {
                case -1: break;        /*  not exist; ok */
                case  0: continue;     /*  error         */
                case  1: error("file %s exists already", RCSfilename);
                         VOID fclose(finptr);
                         continue;
            }
	}
        else  {
            switch( pairfilenames(argc, argv, true, false) ) {
                case -1: continue;    /*  not exist      */
                case  0: continue;    /*  errors         */
                case  1: break;       /*  file exists; ok*/
            }
	}


        /* now RCSfilename contains the name of the RCS file, and
         * workfilename contains the name of the working file.
         * if !initflag, finptr contains the file descriptor for the
         * RCS file. The admin node is initialized.
         */

        diagnose("RCS file: %s", RCSfilename);

        if (!trydiraccess(RCSfilename))            continue; /* give up */
        if (!initflag && !checkaccesslist(caller)) continue; /* give up */
        if (!trysema(RCSfilename,true))            continue; /* give up */

        gettree(); /* read in delta tree */

        /*  update admin. node    */
        if (strict_selected) StrictLocks = strictlock;
        if (commentflag) Comment = commsyml;

        /* update default branch */
        if (branchflag && expandsym(branchsym, branchnum)) {
            if (countnumflds(branchnum)>0) {
                branchdummy.num=branchnum;
                Dbranch = &branchdummy;
            } else
                Dbranch = nil;
        }

        /*  update access list   */
        if ( delaccessflag ) AccessList = nil;
        if ( accessfile ) {
            temp = rplaccessor;
            while( temp ) {
                temptr = temp->nextaccess;
                if ( addnewaccess(temp) )
                    temp->nextaccess = nil;
                temp = temptr;
            }
        }
        temp = rmvaccessor;
        while(temp)   {         /*  remove accessors from accesslist   */
            AccessList = removeaccess(temp, AccessList,true);
            temp = temp->nextaccess;
        }
        temp = newaccessor;
        while( temp)  {         /*  add new accessors   */
            temptr = temp->nextaccess;
            if ( addnewaccess( temp ) )
                temp->nextaccess = nil;
            temp = temptr;
        }

        updateassoc();          /*  update association list   */

        updatelocks();          /*  update locks              */

        /*  update state attribution  */
        if (chgheadstate) {
            /* change state of default branch or head */
            if (Dbranch==nil) {
                if (Head==nil)
                     warn("Can't change states in an empty tree");
                else Head->state = headstate;
            } else {
                rcs_setstate(Dbranch->num,headstate); /* Can't set directly */
            }
        }
        curstate = statelst;
        while( curstate ) {
            rcs_setstate(curstate->revno,curstate->status);
            curstate = curstate->nextstatus;
        }

        cuthead = cuttail = nil;
        if ( delrev && removerevs()) {
            /*  rebuild delta tree if some deltas are deleted   */
            if ( cuttail )
		VOID genrevs(cuttail->num, (char *)nil,(char *)nil,
			     (char *)nil, gendeltas);
            buildtree();
        }


        /* prepare for rewriting the RCS file */
        newRCSfilename=mktempfile(RCSfilename,NEWRCSFILE);
        oldumask = umask(0222); /* turn off write bits */
        if ((frewrite=fopen(newRCSfilename, "w"))==NULL) {
                VOID fclose(finptr);
                error("Can't open file %s",newRCSfilename);
                continue;
        }
        VOID umask(oldumask);
        putadmin(frewrite);
        if ( Head )
           puttree(Head, frewrite);
	putdesc(initflag,textflag,textfile,quietflag);
        rewriteflag = false;

        if ( Head) {
            if (!delrev) {
                /* no revision deleted */
                fastcopy(finptr,frewrite);
            } else {
                if ( cuttail )
                    buildeltatext(gendeltas);
                else
                    scanlogtext((struct hshentry *)nil,empty);
                    /* copy rest of delta text nodes that are not deleted      */
            }
        }
        ffclose(frewrite);   frewrite = NULL;
        if ( ! nerror ) {  /*  move temporary file to RCS file if no error */
	    ignoreints();		/* ignore interrupts */
            if(rename(newRCSfilename,RCSfilename)<0) {
                error("Can't create RCS file %s; saved in %s",
                   RCSfilename, newRCSfilename);
                newRCSfilename[0] = '\0';  /*  avoid deletion by cleanup  */
                restoreints();
                VOID cleanup();
                break;
            }
            newRCSfilename[0]='\0'; /* avoid re-unlinking by cleanup()*/
            /* update mode */
            result=0;
            if (!initflag) /* preserve mode bits */
                result=chmod(RCSfilename,RCSstat.st_mode & ~0222);
            elsif (haveworkstat==0)  /* initialization, and work file exists */
                result=chmod(RCSfilename,workstat.st_mode & ~0222);
            if (result<0) warn("Can't set mode of %s",RCSfilename);

            restoreints();                /* catch them all again */
            diagnose("done");
        } else {
	    diagnose("%s aborted; %s unchanged.",cmdid,RCSfilename);
	    status = 1;
	    nerror = 0;
        }
        } while (cleanup(),
                 ++argv, --argc >=1);

        exit(status);
}       /* end of main (rcs) */



getassoclst(flag, sp)
int     flag;
char    * sp;
/*  Function:   associate a symbolic name to a revision or branch,      */
/*              and store in assoclst                                   */

{
        struct   Symrev  * pt;
        char             * temp, *temp2;
        int                c;

        while( (c=(*++sp)) == ' ' || c == '\t' || c =='\n')  ;
        temp = sp;
        temp2=checkid(sp, ':');  /*  check for invalid symbolic name  */
        sp = temp2; c = *sp;   *sp = '\0';
        while( c == ' ' || c == '\t' || c == '\n')  c = *++sp;

        if ( c != ':' && c != '\0') {
	    error("Invalid string %s after option -n or -N",sp);
            return;
        }

        pt = (struct Symrev *)talloc(sizeof(struct Symrev));
        pt->ssymbol = temp;
        pt->override = flag;
	if (c == '\0')  /*  delete symbol  */
            pt->revno = nil;
        else {
            while( (c = *++sp) == ' ' || c == '\n' || c == '\t')  ;
	    if ( c == '\0' )
                pt->revno = nil;
	    else
                pt->revno = sp;
        }
        pt->nextsym = nil;
        if (lastassoc)
            lastassoc->nextsym = pt;
        else
            assoclst = pt;
        lastassoc = pt;
        return;
}



struct access * getaccessor( sp)
char            *sp;
/*   Function:  get the accessor list of options -e and -a,     */
/*              and store in curpt                              */


{
        struct  access  * curpt, * pt,  *pre;
        char    *temp;
        register c;

        while( ( c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') ;
        if ( c == '\0') {
            error("Missing login name after option -a or -e");
            return nil;
        }

        curpt = pt = nil;
        while( c != '\0') {
                temp=checkid(sp,',');
                pt = (struct access *)talloc(sizeof(struct access));
                pt->login = sp;
                if ( curpt )
                    pre->nextaccess = pt;
                else
                    curpt = pt;
                pt->nextaccess = curpt;
                pre = pt;
                sp = temp;    c = *sp;   *sp = '\0';
                while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
        }
        return pt;
}



getstates(sp)
char    *sp;
/*   Function:  get one state attribute and the corresponding   */
/*              revision and store in statelst                  */

{
        char    *temp, *temp2;
        struct  Status  *pt;
        register        c;

        while( (c=(*++sp)) ==' ' || c == '\t' || c == '\n')  ;
        temp = sp;
        temp2=checkid(sp,':');  /* check for invalid state attribute */
        sp = temp2;   c = *sp;   *sp = '\0';
        while( c == ' ' || c == '\t' || c == '\n' )  c = *++sp;

        if ( c == '\0' ) {  /*  change state of def. branch or Head  */
            chgheadstate = true;
            headstate  = temp;
            return;
        }
        else if ( c != ':' ) {
            error("Missing ':' after state in option -s");
            return;
        }

        while( (c = *++sp) == ' ' || c == '\t' || c == '\n')  ;
        pt = (struct Status *)talloc(sizeof(struct Status));
        pt->status     = temp;
        pt->revno      = sp;
        pt->nextstatus = nil;
        if (laststate)
            laststate->nextstatus = pt;
        else
            statelst = pt;
        laststate = pt;
}



getrplaccess()
/*   Function : get the accesslist of the 'accessfile'  */
/*              and place in rplaccessor                */
{
        register        char    *id, *nextp;
        struct          access  *newaccess, *oldaccess;

        if ( (finptr=fopen(accessfile, "r")) == NULL) {
            faterror("Can't open file %s", accessfile);
        }
        Lexinit();
        nextp = &accessorlst[0];

        if ( ! getkey(Khead)) faterror("Missing head in %s", accessfile);
        VOID getnum();
        if ( ! getlex(SEMI) ) serror("Missing ';' after head in %s",accessfile);

        if (getkey(Kbranch)) { /* optional */
                Dbranch=getnum();
                if (!getlex(SEMI)) serror("Missing ';' after branch list");
        }


#ifdef COMPAT2
        /* read suffix. Only in release 2 format */
        if (getkey(Ksuffix)) {
            if (nexttok==STRING) {
                readstring(); nextlex(); /*through away the suffix*/
            } elsif(nexttok==ID) {
                nextlex();
            }
            if ( ! getlex(SEMI) ) serror("Missing ';' after suffix in %s",accessfile);
        }
#endif

        if (! getkey(Kaccess))fatserror("Missing access list in %s",accessfile);
        oldaccess = nil;
        while( id =getid() ) {
            newaccess = (struct access *)talloc(sizeof(struct access));
            newaccess->login = nextp;
            newaccess->nextaccess = nil;
            while( ( *nextp++ = *id++) != '\0')  ;
            if ( oldaccess )
                oldaccess->nextaccess = newaccess;
            else
                rplaccessor = newaccess;
            oldaccess = newaccess;
        }
        if ( ! getlex(SEMI))serror("Missing ';' after access list in %s",accessfile);
        return;
}



getdelrev(sp)
char    *sp;
/*   Function:  get revision range or branch to be deleted,     */
/*              and place in delrev                             */
{
        int    c;
        struct  delrevpair      *pt;

        if (delrev) free((char *)delrev);

        pt = (struct delrevpair *)talloc(sizeof(struct delrevpair));
        while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;

        if ( c == '<' || c == '-' ) {  /*  -o  -rev  or <rev  */
            while( (c = (*++sp)) == ' ' || c == '\n' || c == '\t')  ;
            pt->strt = sp;    pt->code = 1;
            while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
            *sp = '\0';
            pt->end = nil;  delrev = pt;
            return;
        }
        else {
            pt->strt = sp;
            while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
                   && c != '-' && c != '<' )  c = *++sp;
            *sp = '\0';
            while( c == ' ' || c == '\n' || c == '\t' )  c = *++sp;
            if ( c == '\0' )  {  /*   -o rev or branch   */
                pt->end = nil;   pt->code = 0;
                delrev = pt;
                return;
            }
            if ( c != '-' && c != '<') {
                faterror("Invalid range %s %s after -o", pt->strt, sp);
                free((char *)pt);
                return;
            }
            while( (c = *++sp) == ' ' || c == '\n' || c == '\t')  ;
            if ( c == '\0') {  /*  -o   rev-   or   rev<   */
                pt->end = nil;   pt->code = 2;
                delrev = pt;
                return;
            }
        }
        /*   -o   rev1-rev2    or   rev1<rev2   */
        pt->end = sp;  pt->code = 3;   delrev = pt;
        while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
        *sp = '\0';
}




scanlogtext(delta,func)
struct hshentry * delta; enum stringwork func;
/* Function: Scans delta text nodes up to and including the one given
 * by delta, or up to last one present, if delta==nil.
 * For the one given by delta (if delta!=nil), the log message is saved into
 * curlogmsg and the text is processed according to parameter func.
 * Assumes the initial lexeme must be read in first.
 * Does not advance nexttok after it is finished, except if delta==nil.
 */
{       struct hshentry * nextdelta;

        do {
                rewriteflag = false;
                nextlex();
                if (!(nextdelta=getnum())) {
                    if(delta)
                        faterror("Can't find delta for revision %s", delta->num);
                    else return; /* no more delta text nodes */
                }
                if ( nextdelta->selector != DELETE) {
                        rewriteflag = true;
                        VOID fprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
                }
                if (!getkey(Klog) || nexttok!=STRING)
                        serror("Missing log entry");
                elsif (delta==nextdelta) {
                        VOID savestring(curlogmsg,logsize);
                        delta->log=curlogmsg;
                } else {readstring();
                        if (delta!=nil) delta->log="";
                }
                nextlex();
                if (!getkey(Ktext) || nexttok!=STRING)
                        fatserror("Missing delta text");

                if(delta==nextdelta)
                        /* got the one we're looking for */
                        switch (func) {
                        case copy:      copystring();
                                        break;
                        case edit:      editstring((struct hshentry *)nil);
                                        break;
                        default:        faterror("Wrong scanlogtext");
                        }
                else    readstring(); /* skip over it */

        } while (delta!=nextdelta);
}



releaselst(sourcelst)
struct  access  * sourcelst;
/*   Function:  release the storages whose address are in sourcelst   */

{
        struct  access  * pt;

        pt = sourcelst;
        while(pt) {
	    struct access *pn = pt->nextaccess;
            free((char *)pt);
            pt = pn;
        }
}



struct  Lockrev  * rmnewlocklst(which)
struct  Lockrev  * which;
/*   Function:  remove lock to revision which->revno from newlocklst   */

{
        struct  Lockrev   * pt, *pre;

        while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){
	    struct Lockrev *pn = newlocklst->nextrev;
            free((char *)newlocklst);
	    newlocklst = pn;
        }

        pt = pre = newlocklst;
        while( pt ) {
            if ( ! strcmp(pt->revno, which->revno) ) {
                pre->nextrev = pt->nextrev;
                free((char *)pt);
		pt = pre->nextrev;
            }
            else {
                pre = pt;
                pt = pt->nextrev;
            }
        }
        return pre;
}



struct  access  * removeaccess( who, sourcelst,flag)
struct  access  * who, * sourcelst;
int     flag;
/*   Function:  remove the accessor-- who from sourcelst     */

{
        struct  access  *pt, *pre;

        pt = sourcelst;
        while( pt && (! strcmp(who->login, pt->login) )) {
	    pre = pt->nextaccess;
            free((char *)pt);
	    pt = pre;
            flag = false;
	}
        pre = sourcelst = pt;
        while( pt ) {
            if ( ! strcmp(who->login, pt->login) ) {
		pre->nextaccess = pt->nextaccess;
		free((char *)pt);
		pt = pre->nextaccess;
                flag = false;
            }
            else {
                pre = pt;
                pt = pt->nextaccess;
            }
        }
        if ( flag ) warn("Can't remove a nonexisting accessor %s",who->login);
        return sourcelst;
}



int addnewaccess( who )
struct  access  * who;
/*   Function:  add new accessor-- who into AccessList    */

{
        struct  access  *pt,  *pre;

        pre = pt = AccessList;

        while( pt ) {
            if ( strcmp( who->login, pt->login) ) {
                pre = pt;
                pt = pt->nextaccess;
            }
            else
                return 0;
        }
        if ( pre == pt )
            AccessList = who;
        else
            pre->nextaccess = who;
        return 1;
}


sendmail(Delta, who)
char    * Delta,  *who;
/*   Function:  mail to who, informing him that his lock on delta was
 *   broken by caller. Ask first whether to go ahead. Return false on
 *   error or if user decides not to break the lock.
 */
{
        char    * messagefile;
        int   old1, old2, c, response;
        FILE    * mailmess;


	VOID fprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
        VOID fprintf(stderr, "Do you want to break the lock? [ny](n): ");
        response=c=getchar();
        while (!(c==EOF || c=='\n')) c=getchar();/*skip to end of line*/
        if (response=='\n'||response=='n'||response=='N') return false;

        /* go ahead with breaking  */
        messagefile=mktempfile("/tmp/", "RCSmailXXXXXX");
        if ( (mailmess = fopen(messagefile, "w")) == NULL) {
            faterror("Can't open file %s", messagefile);
        }

	VOID fprintf(mailmess, "Subject: Broken lock on %s\n\n",bindex(RCSfilename,'/'));
        VOID fprintf(mailmess, "Your lock on revision %s of file %s\n",Delta, getfullRCSname());
        VOID fprintf(mailmess,"has been broken by %s for the following reason:\n",caller);
        VOID fputs("State the reason for breaking the lock:\n", stderr);
        VOID fputs("(terminate with ^D or single '.')\n>> ", stderr);

        old1 = '\n';    old2 = ' ';
        for (; ;) {
            c = getchar();
            if ( c == EOF ) {
                VOID putc('\n',stderr);
                VOID fprintf(mailmess, "%c\n", old1);
                break;
            }
            else if ( c == '\n' && old1 == '.' && old2 == '\n')
                break;
            else {
                VOID fputc( old1, mailmess);
                old2 = old1;   old1 = c;
                if (c== '\n') VOID fputs(">> ", stderr);
            }
        }
        ffclose(mailmess);

	/* ignore the exit status, even if delivermail unsuccessful */
        VOID run(messagefile,(char*)nil,
		_PATH_SENDMAIL,
		who,(char*)nil);
        VOID unlink(messagefile);
	return(true);
}



static breaklock(who,delta)
char * who; struct hshentry * delta;
/* function: Finds the lock held by who on delta,
 * and removes it.
 * Sends mail if a lock different from the caller's is broken.
 * Prints an error message if there is no such lock or error.
 */
{
        register struct lock * next, * trail;
        char * num;
        struct lock dummy;
        int whor, numr;

	num=delta->num;
        dummy.nextlock=next=Locks;
        trail = &dummy;
        while (next!=nil) {
		if (num != nil)
			numr = strcmp(num, next->delta->num);

		whor=strcmp(who,next->login);
		if (whor==0 && numr==0) break; /* exact match */
		if (numr==0 && whor !=0) {
                        if (!sendmail( num, next->login)){
                            diagnose("%s still locked by %s",num,next->login);
			    return;
                        } else break; /* continue after loop */
                }
                trail=next;
                next=next->nextlock;
        }
        if (next!=nil) {
                /*found one */
                diagnose("%s unlocked",next->delta->num);
                trail->nextlock=next->nextlock;
                next->delta->lockedby=nil;
                Locks=dummy.nextlock;
        } else  {
		error("no lock set on revision %s", num);
        }
}



struct hshentry *searchcutpt(object, length, store)
char    * object;
int     length;
struct  hshentry   * * store;
/*   Function:  Search store and return entry with number being object. */
/*              cuttail = nil, if the entry is Head; otherwise, cuttail */
/*              is the entry point to the one with number being object  */

{
        while( compartial( (*store++)->num, object, length)  )  ;
        store--;

        if ( *store == Head)
            cuthead = nil;
        else
            cuthead = *(store -1);
        return *store;
}



int  branchpoint(strt, tail)
struct  hshentry        *strt,  *tail;
/*   Function: check whether the deltas between strt and tail	*/
/*		are locked or branch point, return 1 if any is  */
/*		locked or branch point; otherwise, return 0 and */
/*              mark DELETE on selector                         */

{
        struct  hshentry    *pt;
	struct lock  *lockpt;
        int     flag;


        pt = strt;
        flag = false;
        while( pt != tail) {
            if ( pt->branches ){ /*  a branch point  */
                flag = true;
                error("Can't remove branch point %s", pt->num);
            }
	    lockpt = Locks;
	    while(lockpt && lockpt->delta != pt)
		lockpt = lockpt->nextlock;
	    if ( lockpt ) {
		flag = true;
		error("Can't remove locked revision %s",pt->num);
	    }
            pt = pt->next;
        }

        if ( ! flag ) {
            pt = strt;
            while( pt != tail ) {
                pt->selector = DELETE;
                diagnose("deleting revision %s ",pt->num);
                pt = pt->next;
            }
        }
        return flag;
}



removerevs()
/*   Function:  get the revision range to be removed, and place the     */
/*              first revision removed in delstrt, the revision before  */
/*              delstrt in cuthead( nil, if delstrt is head), and the   */
/*              revision after the last removed revision in cuttail(nil */
/*              if the last is a leaf                                   */

{
        struct  hshentry    *target, *target2, * temp, *searchcutpt();
        int     length, flag;

        flag = false;
        if ( ! expandsym(delrev->strt, &numrev[0]) ) return 0;
        target = genrevs(&numrev[0], (char *)nil, (char *)nil, (char *)nil, gendeltas);
        if ( ! target ) return 0;
        if ( cmpnum(target->num, &numrev[0]) ) flag = true;
        length = countnumflds( &numrev[0] );

        if ( delrev->code == 0 ) {  /*  -o  rev    or    -o  branch   */
	    if ( length % 2)
		temp=searchcutpt(target->num,length+1,gendeltas);
	    else if (flag) {
                error("Revision %s does not exist", &numrev[0]);
		return 0;
	    }
	    else
		temp = searchcutpt(&numrev[0],length,gendeltas);
	    cuttail = target->next;
            if ( branchpoint(temp, cuttail) ) {
                cuttail = nil;
                return 0;
            }
            delstrt = temp;     /* first revision to be removed   */
            return 1;
        }

        if ( length % 2 ) {   /*  invalid branch after -o   */
            error("Invalid branch range %s after -o", &numrev[0]);
            return 0;
        }

        if ( delrev->code == 1 )  {  /*  -o  -rev   */
            if ( length > 2 ) {
                temp = searchcutpt( target->num, length-1, gendeltas);
                cuttail = target->next;
            }
            else {
                temp = searchcutpt(target->num, length, gendeltas);
                cuttail = target;
                while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
                    cuttail = cuttail->next;
            }
            if ( branchpoint(temp, cuttail) ){
                cuttail = nil;
                return 0;
            }
            delstrt = temp;
            return 1;
        }

        if ( delrev->code == 2 )  {   /*  -o  rev-   */
            if ( length == 2 ) {
                temp = searchcutpt(target->num, 1,gendeltas);
                if ( flag)
                    cuttail = target;
                else
                    cuttail = target->next;
            }
            else  {
                if ( flag){
                    cuthead = target;
                    if ( !(temp = target->next) ) return 0;
                }
                else
                    temp = searchcutpt(target->num, length, gendeltas);
                getbranchno(temp->num, &numrev[0]);  /*  get branch number  */
                target = genrevs(&numrev[0], (char *)nil, (char *)nil, (char *)nil, gendeltas);
            }
            if ( branchpoint( temp, cuttail ) ) {
                cuttail = nil;
                return 0;
            }
            delstrt = temp;
            return 1;
        }

        /*   -o   rev1-rev2   */
        if ( ! expandsym(delrev->end, &numrev[0])  )  return 0;
        if ( length != countnumflds( &numrev[0] ) ) {
            error("Invalid revision range %s-%s", target->num, &numrev[0]);
            return 0;
        }
        if ( length > 2 && compartial( &numrev[0], target->num, length-1) ) {
            error("Invalid revision range %s-%s", target->num, &numrev[0]);
            return 0;
        }

        target2 = genrevs( &numrev[0], (char *)nil, (char *)nil, (char *)nil,gendeltas);
        if ( ! target2 ) return 0;

        if ( length > 2) {  /* delete revisions on branches  */
            if ( cmpnum(target->num, target2->num) > 0) {
                if ( cmpnum(target2->num, &numrev[0]) )
                    flag = true;
                else
                    flag = false;
                temp = target;
                target = target2;
                target2 = temp;
            }
            if ( flag ) {
                if ( ! cmpnum(target->num, target2->num) ) {
                    error("Revisions %s-%s don't exist", delrev->strt,delrev->end);
                    return 0;
                }
                cuthead = target;
                temp = target->next;
            }
            else
                temp = searchcutpt(target->num, length, gendeltas);
            cuttail = target2->next;
        }
        else { /*  delete revisions on trunk  */
            if ( cmpnum( target->num, target2->num) < 0 ) {
                temp = target;
                target = target2;
                target2 = temp;
            }
            else
                if ( cmpnum(target2->num, &numrev[0]) )
                    flag = true;
                else
                    flag = false;
            if ( flag ) {
                if ( ! cmpnum(target->num, target2->num) ) {
                    error("Revisions %s-%s don't exist", delrev->strt, delrev->end);
                    return 0;
                }
                cuttail = target2;
            }
            else
                cuttail = target2->next;
            temp = searchcutpt(target->num, length, gendeltas);
        }
        if ( branchpoint(temp, cuttail) )  {
            cuttail = nil;
            return 0;
        }
        delstrt = temp;
        return 1;
}



updateassoc()
/*   Function: add or delete(if revno is nil) association	*/
/*		which is stored in assoclst			*/

{
        struct  Symrev  * curassoc;
	struct  assoc   * pre,  * pt;
        struct  hshentry    * target;

        /*  add new associations   */
        curassoc = assoclst;
        while( curassoc ) {
            if ( curassoc->revno == nil ) {  /* delete symbol  */
		pre = pt = Symbols;
                while( pt && strcmp(pt->symbol,curassoc->ssymbol) ) {
		    pre = pt;
		    pt = pt->nextassoc;
		}
		if ( pt )
		    if ( pre == pt )
			Symbols = pt->nextassoc;
		    else
			pre->nextassoc = pt->nextassoc;
		else
                    warn("Can't delete nonexisting symbol %s",curassoc->ssymbol);
	    }
            else if ( expandsym( curassoc->revno, &numrev[0] ) ) {
	    /*   add symbol  */
               target = (struct hshentry *) talloc(sizeof(struct hshentry));
               target->num = &numrev[0];
               VOID addsymbol(target, curassoc->ssymbol, curassoc->override);
            }
            curassoc = curassoc->nextsym;
        }

}



updatelocks()
/* Function: remove lock for caller or first lock if unlockcaller==true;
 *           remove locks which are stored in rmvlocklst,
 *           add new locks which are stored in newlocklst,
 *           add lock for Dbranch or Head if lockhead==true.
 */
{
        struct  hshentry        *target;
        struct  Lockrev         *lockpt;

        if(unlockcaller == true) { /*  find lock for caller  */
            if ( Head ) {
		if (Locks) {
		    target=findlock(caller,true);
		    if (target==nil) {
			breaklock(caller, Locks->delta); /* remove most recent lock */
		    } else {
			diagnose("%s unlocked",target->num);
		    }
		} else {
		    warn("There are no locks set.");
		}
            } else {
                warn("Can't unlock an empty tree");
            }
        }

        /*  remove locks which are stored in rmvlocklst   */
        lockpt = rmvlocklst;
        while( lockpt ) {
	    if (expandsym(lockpt->revno, numrev)) {
		target = genrevs(numrev, (char *)nil, (char *)nil, (char *)nil, gendeltas);
                if ( target )
		   if ( !(countnumflds(numrev)%2) && cmpnum(target->num,numrev))
			error("Can't unlock nonexisting revision %s",lockpt->revno);
                   else
                        breaklock(caller, target);
                        /* breaklock does its own diagnose */
            }
            lockpt = lockpt->nextrev;
        }

        /*  add new locks which stored in newlocklst  */
        lockpt = newlocklst;
        while( lockpt ) {
            setlock(lockpt->revno,caller);
            lockpt = lockpt->nextrev;
        }

        if ( lockhead == true) {  /*  lock default branch or head  */
            if (Dbranch) {
                setlock(Dbranch->num,caller);
            } elsif ( Head) {
                if (addlock(Head, caller))
                    diagnose("%s locked",Head->num);
            } else {
                warn("Can't lock an empty tree");
            }
        }

}



setlock(rev,who)
char * rev, * who;
/* Function: Given a revision or branch number, finds the correponding
 * delta and locks it for who.
 */
{
        struct  lock     *lpt;
        struct  hshentry *target;

        if (expandsym(rev, &numrev[0]) ){
            target = genrevs(&numrev[0],(char *) nil,(char *) nil,
			     (char *)nil, gendeltas);
            if ( target )
               if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num,&numrev[0]))
                    error("Can't lock nonexisting revision %s",numrev);
               else
                    if(lpt=addlock(target, who))
                        diagnose("%s locked",lpt->delta->num);
        }
}



rcs_setstate(rev,status)
char * rev, * status;
/* Function: Given a revision or branch number, finds the corresponding delta
 * and sets its state to status.
 */
{
        struct  hshentry *target;

        if ( expandsym(rev, &numrev[0]) ) {
            target = genrevs(&numrev[0],(char *) nil, (char *)nil,
			     (char *) nil, gendeltas);
            if ( target )
               if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num, &numrev[0]) )
                    error("Can't set state of nonexisting revision %s to %s",
                           numrev,status);
               else
                    target->state = status;
        }
}





buildeltatext(deltas)
struct  hshentry        ** deltas;
/*   Function:  put the delta text on frewrite and make necessary   */
/*              change to delta text                                */
{
        int  i, c, exit_stats;

        cuttail->selector = DELETE;
        initeditfiles("/tmp/");
        scanlogtext(deltas[0], copy);
        i = 1;
        if ( cuthead )  {
            cutfilename=mktempfile("/tmp/", "RCScutXXXXXX");
            if ( (fcut = fopen(cutfilename, "w")) == NULL) {
                faterror("Can't open temporary file %s", cutfilename);
            }

            while( deltas[i-1] != cuthead )  {
                scanlogtext(deltas[i++], edit);
            }

            finishedit((struct hshentry *)nil);    rewind(fcopy);
            while( (c = getc(fcopy)) != EOF) VOID putc(c, fcut);
            swapeditfiles(false);
            ffclose(fcut);
        }

        while( deltas[i-1] != cuttail)
            scanlogtext(deltas[i++], edit);
        finishedit((struct hshentry *)nil);    ffclose(fcopy);

        if ( cuthead ) {
            diffilename=mktempfile("/tmp/", "RCSdifXXXXXX");
            exit_stats = run((char*)nil,diffilename,
			DIFF,"-n",cutfilename,resultfile,(char*)nil);
            if (exit_stats != 0 && exit_stats != (1 << BYTESIZ))
                faterror ("diff failed");
            if(!putdtext(cuttail->num,curlogmsg,diffilename,frewrite)) return;
        }
        else
            if (!putdtext(cuttail->num,curlogmsg,resultfile,frewrite)) return;

        scanlogtext((struct hshentry *)nil,empty); /* read the rest of the deltas */
}



buildtree()
/*   Function:  actually removes revisions whose selector field  */
/*              is DELETE, and rebuilds  the linkage of deltas.  */
/*              asks for reconfirmation if deleting last revision*/
{
	int c,  response;

	struct	hshentry   * Delta;
        struct  branchhead      *pt, *pre;

        if ( cuthead )
           if ( cuthead->next == delstrt )
                cuthead->next = cuttail;
           else {
                pre = pt = cuthead->branches;
                while( pt && pt->hsh != delstrt )  {
                    pre = pt;
                    pt = pt->nextbranch;
                }
                if ( cuttail )
                    pt->hsh = cuttail;
                else if ( pt == pre )
                    cuthead->branches = pt->nextbranch;
                else
                    pre->nextbranch = pt->nextbranch;
            }
	else {
            if ( cuttail == nil && !quietflag) {
                VOID fprintf(stderr,"Do you really want to delete all revisions ?[ny](n): ");
		c = response = getchar();
		while( c != EOF && c != '\n') c = getchar();
                if ( response != 'y' && response != 'Y') {
                    diagnose("No revision deleted");
		    Delta = delstrt;
		    while( Delta) {
			Delta->selector = 'S';
			Delta = Delta->next;
		    }
		    return;
		}
	    }
            Head = cuttail;
	}
        return;
}

