From 098fc5cd586c12c42406e4049b7baa873f11cd71 Mon Sep 17 00:00:00 2001 From: "William F. Jolitz" Date: Tue, 15 Aug 1989 21:34:15 -0800 Subject: [PATCH] 386BSD 0.1 development Work on file usr/src/usr.bin/rcs/src/rcs.c Co-Authored-By: Lynne Greer Jolitz Synthesized-from: 386BSD-0.1 --- usr/src/usr.bin/rcs/src/rcs.c | 1559 +++++++++++++++++++++++++++++++++ 1 file changed, 1559 insertions(+) create mode 100644 usr/src/usr.bin/rcs/src/rcs.c diff --git a/usr/src/usr.bin/rcs/src/rcs.c b/usr/src/usr.bin/rcs/src/rcs.c new file mode 100644 index 0000000000..fd2ad97093 --- /dev/null +++ b/usr/src/usr.bin/rcs/src/rcs.c @@ -0,0 +1,1559 @@ +/* + * 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 (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 +#include +#include "rcsbase.h" +#include +#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 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 rev1end = 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; +} + -- 2.20.1