1: /*
2: * RCS create/change operation
3: */
4: static char rcsid[]=
5: "$Header: /usr/wft/RCS/SRC/RCS/rcs.c,v 3.9 83/02/15 15:38:39 wft Exp $ Purdue CS";
6: /***************************************************************************
7: * create RCS files or change RCS file attributes
8: * Compatibility with release 2: define COMPAT2
9: ***************************************************************************
10: *
11: * Copyright (C) 1982 by Walter F. Tichy
12: * Purdue University
13: * Computer Science Department
14: * West Lafayette, IN 47907
15: *
16: * All rights reserved. No part of this software may be sold or distributed
17: * in any form or by any means without the prior written permission of the
18: * author.
19: */
20:
21:
22:
23: /* $Log: rcs.c,v $
24: * Revision 3.9 83/02/15 15:38:39 wft
25: * Added call to fastcopy() to copy remainder of RCS file.
26: *
27: * Revision 3.8 83/01/18 17:37:51 wft
28: * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
29: *
30: * Revision 3.7 83/01/15 18:04:25 wft
31: * Removed putree(); replaced with puttree() in rcssyn.c.
32: * Combined putdellog() and scanlogtext(); deleted putdellog().
33: * Cleaned up diagnostics and error messages. Fixed problem with
34: * mutilated files in case of deletions in 2 files in a single command.
35: * Changed marking of selector from 'D' to DELETE.
36: *
37: * Revision 3.6 83/01/14 15:37:31 wft
38: * Added ignoring of interrupts while new RCS file is renamed;
39: * Avoids deletion of RCS files by interrupts.
40: *
41: * Revision 3.5 82/12/10 21:11:39 wft
42: * Removed unused variables, fixed checking of return code from diff,
43: * introduced variant COMPAT2 for skipping Suffix on -A files.
44: *
45: * Revision 3.4 82/12/04 13:18:20 wft
46: * Replaced getdelta() with gettree(), changed breaklock to update
47: * field lockedby, added some diagnostics.
48: *
49: * Revision 3.3 82/12/03 17:08:04 wft
50: * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
51: * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
52: * fixed -u for missing revno. Disambiguated structure members.
53: *
54: * Revision 3.2 82/10/18 21:05:07 wft
55: * rcs -i now generates a file mode given by the umask minus write permission;
56: * otherwise, rcs keeps the mode, but removes write permission.
57: * I added a check for write error, fixed call to getlogin(), replaced
58: * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
59: * conflicting, long identifiers.
60: *
61: * Revision 3.1 82/10/13 16:11:07 wft
62: * fixed type of variables receiving from getc() (char -> int).
63: */
64:
65:
66: #include <pwd.h>
67: #include <sys/types.h>
68: #include <sys/stat.h>
69: #include <sysexits.h>
70: #include "rcsbase.h"
71: static char rcsbaseid[] = RCSBASE;
72:
73:
74: extern FILE * fopen();
75: extern curdir();
76: extern char * bindex();
77: extern int expandsym(); /* get numeric revision name */
78: extern struct hshentry * getnum();
79: extern struct lock * addlock(); /* add a lock */
80: extern char * getid();
81: extern char * getkeyval();
82: extern char * Klog, *Khead, *Kaccess, *Ksuffix, *Ktext;
83: extern struct passwd *getpwuid();
84: extern char * malloc();
85: extern struct hshentry * genrevs();
86: extern struct hshentry * breaklock(); /* remove locks */
87: extern char * checkid(); /* check an identifier */
88: extern char * getfullRCSname(); /* get full path name of RCS file */
89: extern char * mktempfile(); /* temporary file name generator */
90: extern free();
91: extern int nextc; /* next input character */
92: extern int nerror; /* counter for errors */
93: extern int quietflag; /* diagnoses suppressed if true */
94: extern char curlogmsg[]; /* current log message */
95: extern char * resultfile, *editfile; /* filename for fcopy and fedit */
96: extern FILE *fcopy; /* result file during editing */
97: extern FILE *fedit; /* edit file */
98: extern FILE * finptr; /* RCS input file */
99: extern FILE * frewrite; /* new RCS file */
100:
101: char * RCSfilename, * workfilename;
102: char * newRCSfilename, * diffilename, * cutfilename;
103: char accessorlst[strtsize];
104:
105: FILE * fcut; /* temporary file to rebuild delta tree */
106: int rewriteflag; /* indicates whether input should be echoed to frewrite */
107: struct stat filestatus; /* used for preserving mode of an exisiting RCS file*/
108: int oldumask; /* saves umask */
109:
110: int initflag, strictlock, strict_selected, textflag;
111: char * textfile, * accessfile;
112: char * caller, numrev[30]; /* caller's login; */
113: struct access * newaccessor, * rmvaccessor, * rplaccessor;
114: struct access *curaccess, *rmaccess;
115: struct hshentry * gendeltas[hshsize];
116:
117: struct Lockrev {
118: char * revno;
119: struct Lockrev * nextrev;
120: };
121:
122: struct Symrev {
123: char * revno;
124: char * ssymbol;
125: int override;
126: struct Symrev * nextsym;
127: };
128:
129: struct Status {
130: char * revno;
131: char * status;
132: struct Status * nextstatus;
133: };
134:
135: struct delrevpair {
136: char * strt;
137: char * end;
138: int code;
139: };
140:
141: struct Lockrev * newlocklst, * rmvlocklst;
142: struct Symrev * assoclst, * lastassoc;
143: struct Status * statelst, * laststate;
144: struct delrevpair * delrev;
145: struct hshentry * cuthead, *cuttail, * delstrt;
146: char command[80], * commsyml;
147: char * headstate;
148: int headoverride, lockhead, unlockcaller, chgheadstate, ;
149: int delaccessflag;
150: enum stringwork {copy, edit, empty}; /* expand and edit_expand not needed */
151:
152:
153: main (argc, argv)
154: int argc;
155: char * argv[];
156: {
157: char *comdusge;
158: struct access *removeaccess(), * getaccessor();
159: struct Lockrev *rmnewlocklst();
160: struct Lockrev *curlock, * rmvlock, *lockpt;
161: struct Status * curstate;
162: struct hshentry * target;
163: struct access *temp, *temptr;
164:
165: nerror = 0;
166: catchints();
167: cmdid = "rcs";
168: quietflag = false;
169: comdusge ="command format:\nrcs -i -alogins -Alogins -e[logins] -c[commentleader] -l[rev] -u[rev] -L -U -nname[:rev] -Nname[:rev] -orange -sstate[:rev] -t[textfile] file....";
170: rplaccessor = nil; delstrt = nil;
171: accessfile = textfile = caller = nil;
172: commentflag = chgheadstate = false;
173: lockhead = false; unlockcaller=false;
174: initflag= textflag = false;
175: strict_selected = 0;
176:
177: caller=getpwuid(getuid())->pw_name;
178: laststate = statelst = nil;
179: lastassoc = assoclst = nil;
180: curlock = rmvlock = newlocklst = rmvlocklst = nil;
181: curaccess = rmaccess = rmvaccessor = newaccessor = nil;
182: delaccessflag = false;
183:
184: /* preprocessing command options */
185: while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
186: switch ((*argv)[1]) {
187:
188: case 'i': /* initail version */
189: initflag = true;
190: break;
191:
192: case 'c': /* change comment symbol */
193: if (commentflag)warn("Redefinition of option -c");
194: commentflag = true;
195: commsyml = (*argv)+2;
196: break;
197:
198: case 'a': /* add new accessor */
199: if ( (*argv)[2] == '\0') {
200: error("Login name missing after -a");
201: }
202: if ( (temp = getaccessor((*argv)+1)) ) {
203: if ( newaccessor )
204: curaccess->nextaccess = temp->nextaccess;
205: else
206: newaccessor = temp->nextaccess;
207: temp->nextaccess = nil;
208: curaccess = temp;
209: }
210: break;
211:
212: case 'A': /* append access list according to accessfile */
213: if ( (*argv)[2] == '\0') {
214: error("Missing file name after -A");
215: break;
216: }
217: if ( accessfile) warn("Redefinition of option -A");
218: *argv = *argv+2;
219: if( pairfilenames(1, argv, true, false) > 0) {
220: releaselst(newaccessor);
221: newaccessor = curaccess = nil;
222: releaselst(rmvaccessor);
223: rmvaccessor = rmaccess = nil;
224: accessfile = RCSfilename;
225: }
226: else
227: accessfile = nil;
228: break;
229:
230: case 'e': /* remove accessors */
231: if ( (*argv)[2] == '\0' ) {
232: delaccessflag = true;
233: break;
234: }
235: if ( (temp = getaccessor((*argv)+1)) ) {
236: if ( rmvaccessor )
237: rmaccess->nextaccess = temp->nextaccess;
238: else
239: rmvaccessor = temp->nextaccess;
240: temptr = temp->nextaccess;
241: temp->nextaccess = nil;
242: rmaccess = temp;
243: while( temptr ) {
244: newaccessor = removeaccess(temptr,newaccessor,false);
245: temptr = temptr->nextaccess;
246: }
247: curaccess = temp = newaccessor;
248: while( temp){
249: curaccess = temp;
250: temp = temp->nextaccess;
251: }
252: }
253: break;
254:
255: case 'l': /* lock a revision if it is unlocked */
256: if ( (*argv)[2] == '\0' ){ /* lock head */
257: lockhead = true;
258: break;
259: }
260: lockpt = (struct Lockrev *)malloc(sizeof(struct Lockrev));
261: lockpt->revno = (*argv)+2;
262: lockpt->nextrev = nil;
263: if ( curlock )
264: curlock->nextrev = lockpt;
265: else
266: newlocklst = lockpt;
267: curlock = lockpt;
268: break;
269:
270: case 'u': /* release lock of a locked revision */
271: if ( (*argv)[2] == '\0'){ /* unlock head */
272: unlockcaller=true;
273: break;
274: }
275: lockpt = (struct Lockrev *)malloc(sizeof(struct Lockrev));
276: lockpt->revno = (*argv)+2;
277: lockpt->nextrev = nil;
278: if (rmvlock)
279: rmvlock->nextrev = lockpt;
280: else
281: rmvlocklst = lockpt;
282: rmvlock = lockpt;
283:
284: curlock = rmnewlocklst(lockpt);
285: break;
286:
287: case 'L': /* set strict locking */
288: if (strict_selected++) { /* Already selected L or U? */
289: if (!strictlock) /* Already selected -U? */
290: warn("Option -L overrides -U");
291: }
292: strictlock = true;
293: break;
294:
295: case 'U': /* release strict locking */
296: if (strict_selected++) { /* Already selected L or U? */
297: if (strictlock) /* Already selected -L? */
298: warn("Option -L overrides -U");
299: }
300: else
301: strictlock = false;
302: break;
303:
304: case 'n': /* add new association: error, if name exists */
305: if ( (*argv)[2] == '\0') {
306: error("Missing symbolic name after -n");
307: break;
308: }
309: getassoclst(false, (*argv)+1);
310: break;
311:
312: case 'N': /* add or change association */
313: if ( (*argv)[2] == '\0') {
314: error("Missing symbolic name after -N");
315: break;
316: }
317: getassoclst(true, (*argv)+1);
318: break;
319:
320: case 'o': /* delete revisins */
321: if (delrev) warn("Redefinition of option -o");
322: if ( (*argv)[2] == '\0' ) {
323: error("Missing revision range after -o");
324: break;
325: }
326: getdelrev( (*argv)+1 );
327: break;
328:
329: case 's': /* change state attribute of a revision */
330: if ( (*argv)[2] == '\0') {
331: error("State missing after -s");
332: break;
333: }
334: getstates( (*argv)+1);
335: break;
336:
337: case 't': /* change descriptive text */
338: textflag=true;
339: if ((*argv)[2]!='\0'){
340: if (textfile!=nil)warn("Redefinition of -t option");
341: textfile = (*argv)+2;
342: }
343: break;
344:
345: case 'q':
346: quietflag = true;
347: break;
348: default:
349: faterror("Unknown option: %s\n%s", *argv, comdusge);
350: };
351: } /* end processing of options */
352:
353: if (argc<1) faterror("No input file\n%s", comdusge);
354: if (nerror) { /* exit, if any error in command options */
355: diagnose("%s aborted",cmdid);
356: exit(1);
357: }
358: if (accessfile) /* get replacement for access list */
359: getrplaccess();
360: if (nerror) {
361: diagnose("%s aborted",cmdid);
362: exit(1);
363: }
364:
365: /* now handle all filenames */
366: do {
367: rewriteflag = false;
368: finptr=frewrite=NULL;
369: nerror=0;
370:
371: if ( initflag ) {
372: switch( pairfilenames(argc, argv, false, false) ) {
373: case -1: break;
374: case 0: continue; /* can't open */
375: case 1: error("file %s exists already", RCSfilename);
376: fclose(finptr);
377: continue;
378: }
379: }
380: else {
381: switch( pairfilenames(argc, argv, true, false) ) {
382: case -1: continue; /* not exist */
383: case 0: continue; /* can't open */
384: case 1: /* file exists */
385: fstat(fileno(finptr), &filestatus);/*grab mode*/
386: break;
387: }
388: }
389:
390:
391: /* now RCSfilename contains the name of the RCS file, and
392: * workfilename contains the name of the working file.
393: * if !initflag, finptr contains the file descriptor for the
394: * RCS file. The admin node is initialized.
395: */
396:
397: diagnose("RCS file: %s", RCSfilename);
398:
399: if (!trydiraccess(RCSfilename)) continue; /* give up */
400: if (!initflag && !checkaccesslist(caller)) continue; /* give up */
401: if (!trysema(RCSfilename,true)) continue; /* give up */
402:
403: gettree(); /* read in delta tree */
404:
405: /* update admin. node */
406: if (strict_selected) StrictLocks = strictlock;
407: if (commentflag) Comment = commsyml;
408:
409: /* update access list */
410: if ( delaccessflag ) AccessList = nil;
411: if ( accessfile ) {
412: temp = rplaccessor;
413: while( temp ) {
414: temptr = temp->nextaccess;
415: if ( addnewaccess(temp) )
416: temp->nextaccess = nil;
417: temp = temptr;
418: }
419: }
420: temp = rmvaccessor;
421: while(temp) { /* remove accessors from accesslist */
422: AccessList = removeaccess(temp, AccessList,true);
423: temp = temp->nextaccess;
424: }
425: temp = newaccessor;
426: while( temp) { /* add new accessors */
427: temptr = temp->nextaccess;
428: if ( addnewaccess( temp ) )
429: temp->nextaccess = nil;
430: temp = temptr;
431: }
432:
433: updateassoc(); /* update association list */
434:
435: if ( lockhead == true) { /* lock head */
436: if ( Head) {
437: if (addlock(Head, caller))
438: diagnose("%s locked",Head->num);
439: } else {
440: warn("Can't lock an empty tree");
441: }
442: }
443: if(unlockcaller == true) { /* find lock for caller */
444: if ( Head ) {
445: breaklock(caller, nil);
446: /* breaklock does it's own diagnose */
447: } else {
448: warn("Can't unlock an empty tree");
449: }
450: }
451: updatelock();
452:
453: /* update state attribution */
454: if (chgheadstate && Head) Head->state = headstate;
455: curstate = statelst;
456: while( curstate ) {
457: if ( expandsym(curstate->revno, &numrev[0]) ) {
458: target = genrevs(&numrev[0], nil, nil, nil, gendeltas);
459: if ( target )
460: if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num, &numrev[0]) )
461: error("Can't set state %s of a nonexistent revision %s",
462: curstate->status, curstate->revno);
463: else
464: target->state = curstate->status;
465: }
466: curstate = curstate->nextstatus;
467: }
468:
469: cuthead = cuttail = nil;
470: if ( delrev && removerevs()) {
471: /* rebuild delta tree if some deltas are deleted */
472: if ( cuttail ) genrevs(cuttail->num, nil,nil, nil, gendeltas);
473: buildtree();
474: }
475:
476:
477: /* prepare for rewriting the RCS file */
478: newRCSfilename=mktempfile(RCSfilename,NEWRCSFILE);
479: oldumask = umask(0222); /* turn off write bits */
480: if ((frewrite=fopen(newRCSfilename, "w"))==NULL) {
481: fclose(finptr);
482: error("Can't open file %s",newRCSfilename);
483: continue;
484: }
485: umask(oldumask);
486: putadmin(frewrite);
487: if ( Head )
488: puttree(Head, frewrite);
489: putdesc(initflag,textflag,textfile,quietflag);
490: rewriteflag = false;
491:
492: if ( Head) {
493: if (!delrev) {
494: /* no revision deleted */
495: fastcopy(finptr,frewrite);
496: } else {
497: if ( cuttail )
498: buildeltatext(gendeltas);
499: else
500: scanlogtext(nil,empty);
501: /* copy rest of delta text nodes that are not deleted */
502: }
503: }
504: ffclose(frewrite); frewrite = NULL;
505: if ( ! nerror ) { /* move temporary file to RCS file if no error */
506: ignoreints(); /* ignore interrupts */
507: if(rename(newRCSfilename,RCSfilename)<0) {
508: error("Can't create RCS file %s; saved in %s",
509: RCSfilename, newRCSfilename);
510: newRCSfilename[0] = '\0'; /* avoid deletion by cleanup */
511: catchints();
512: cleanup();
513: break;
514: }
515: newRCSfilename[0]='\0'; /* avoid re-unlinking by cleanup()*/
516: if (!initflag) /* preserve mode bits */
517: if (chmod(RCSfilename,filestatus.st_mode & ~0222)<0)
518: warn("Can't set mode of %s",RCSfilename);
519:
520: catchints(); /* catch them all again */
521: diagnose("done");
522: } else {
523: diagnose("%s unchanged.",RCSfilename);
524: }
525: } while (cleanup(),
526: ++argv, --argc >=1);
527:
528: exit(nerror!=0);
529: } /* end of main (rcs) */
530:
531:
532:
533: getassoclst(flag, sp)
534: int flag;
535: char * sp;
536: /* Function: associate a symbolic name to a revision or branch, */
537: /* and store in assoclst */
538:
539: {
540: struct Symrev * pt;
541: char * temp, *temp2;
542: int c;
543:
544: while( (c=(*++sp)) == ' ' || c == '\t' || c =='\n') ;
545: temp = sp;
546: temp2=checkid(sp, ':'); /* check for invalid symbolic name */
547: sp = temp2; c = *sp; *sp = '\0';
548: while( c == ' ' || c == '\t' || c == '\n') c = *++sp;
549:
550: if ( c != ':' && c != '\0') {
551: error("Invalid string %s after option -n or -N",sp);
552: return;
553: }
554:
555: pt = (struct Symrev *)malloc(sizeof(struct Symrev));
556: pt->ssymbol = temp;
557: pt->override = flag;
558: if (c == '\0') /* delete symbol */
559: pt->revno = nil;
560: else {
561: while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
562: if ( c == '\0' )
563: pt->revno = nil;
564: else
565: pt->revno = sp;
566: }
567: pt->nextsym = nil;
568: if (lastassoc)
569: lastassoc->nextsym = pt;
570: else
571: assoclst = pt;
572: lastassoc = pt;
573: return;
574: }
575:
576:
577:
578: struct access * getaccessor( sp)
579: char *sp;
580: /* Function: get the accessor list of options -e and -a, */
581: /* and store in curpt */
582:
583:
584: {
585: struct access * curpt, * pt, *pre;
586: char *temp;
587: register c;
588:
589: while( ( c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') ;
590: if ( c == '\0') {
591: error("Missing login name after option -a or -e");
592: return nil;
593: }
594:
595: curpt = pt = nil;
596: while( c != '\0') {
597: temp=checkid(sp,',');
598: pt = (struct access *)malloc(sizeof(struct access));
599: pt->login = sp;
600: if ( curpt )
601: pre->nextaccess = pt;
602: else
603: curpt = pt;
604: pt->nextaccess = curpt;
605: pre = pt;
606: sp = temp; c = *sp; *sp = '\0';
607: while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
608: }
609: return pt;
610: }
611:
612:
613:
614: getstates(sp)
615: char *sp;
616: /* Function: get one state attribute and the corresponding */
617: /* revision and store in statelst */
618:
619: {
620: char *temp, *temp2;
621: struct Status *pt;
622: register c;
623:
624: while( (c=(*++sp)) ==' ' || c == '\t' || c == '\n') ;
625: temp = sp;
626: temp2=checkid(sp,':'); /* check for invalid state attribute */
627: sp = temp2; c = *sp; *sp = '\0';
628: while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp;
629:
630: if ( c == '\0' ) { /* state attribute of Head */
631: chgheadstate = true;
632: headstate = temp;
633: return;
634: }
635: else if ( c != ':' ) {
636: error("Missing ':' after state in option -s");
637: return;
638: }
639:
640: while( (c = *++sp) == ' ' || c == '\t' || c == '\n') ;
641: pt = (struct Status *)malloc(sizeof(struct Status));
642: pt->status = temp;
643: pt->revno = sp;
644: pt->nextstatus = nil;
645: if (laststate)
646: laststate->nextstatus = pt;
647: else
648: statelst = pt;
649: laststate = pt;
650: }
651:
652:
653:
654: getrplaccess()
655: /* Function : get the accesslist of the 'accessfile' */
656: /* and place in rplaccessor */
657: {
658: register char *id, *nextp;
659: struct access *newaccess, *curaccess;
660:
661: if ( (finptr=fopen(accessfile, "r")) == NULL) {
662: faterror("Can't open file %s", accessfile);
663: }
664: Lexinit();
665: nextp = &accessorlst[0];
666:
667: if ( ! getkey(Khead)) faterror("Missing head in %s", accessfile);
668: getnum();
669: if ( ! getlex(SEMI) ) serror("Missing ';' after head in %s",accessfile);
670:
671: #ifdef COMPAT2
672: /* read suffix. Only in release 2 format */
673: if (getkey(Ksuffix)) {
674: if (nexttok==STRING) {
675: readstring(); nextlex(); /*through away the suffix*/
676: } elsif(nexttok==ID) {
677: nextlex();
678: }
679: if ( ! getlex(SEMI) ) serror("Missing ';' after suffix in %s",accessfile);
680: }
681: #endif
682:
683: if (! getkey(Kaccess))fatserror("Missing access list in %s",accessfile);
684: curaccess = nil;
685: while( id =getid() ) {
686: newaccess = (struct access *)malloc(sizeof(struct access));
687: newaccess->login = nextp;
688: newaccess->nextaccess = nil;
689: while( ( *nextp++ = *id++) != '\0') ;
690: if ( curaccess )
691: curaccess->nextaccess = newaccess;
692: else
693: rplaccessor = newaccess;
694: curaccess = newaccess;
695: }
696: if ( ! getlex(SEMI))serror("Missing ';' after access list in %s",accessfile);
697: return;
698: }
699:
700:
701:
702: getdelrev(sp)
703: char *sp;
704: /* Function: get revision range or branch to be deleted, */
705: /* and place in delrev */
706: {
707: int c;
708: struct delrevpair *pt;
709:
710: if (delrev) free(delrev);
711:
712: pt = (struct delrevpair *)malloc(sizeof(struct delrevpair));
713: while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
714:
715: if ( c == '<' || c == '-' ) { /* -o -rev or <rev */
716: while( (c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
717: pt->strt = sp; pt->code = 1;
718: while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
719: *sp = '\0';
720: pt->end = nil; delrev = pt;
721: return;
722: }
723: else {
724: pt->strt = sp;
725: while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
726: && c != '-' && c != '<' ) c = *++sp;
727: *sp = '\0';
728: while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp;
729: if ( c == '\0' ) { /* -o rev or branch */
730: pt->end = nil; pt->code = 0;
731: delrev = pt;
732: return;
733: }
734: if ( c != '-' && c != '<') {
735: faterror("Invalid range %s %s after -o", pt->strt, sp);
736: free(pt);
737: return;
738: }
739: while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
740: if ( c == '\0') { /* -o rev- or rev< */
741: pt->end = nil; pt->code = 2;
742: delrev = pt;
743: return;
744: }
745: }
746: /* -o rev1-rev2 or rev1<rev2 */
747: pt->end = sp; pt->code = 3; delrev = pt;
748: while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
749: *sp = '\0';
750: }
751:
752:
753:
754:
755: scanlogtext(delta,func)
756: struct hshentry * delta; enum stringwork func;
757: /* Function: Scans delta text nodes up to and including the one given
758: * by delta, or up to last one present, if delta==nil.
759: * For the one given by delta (if delta!=nil), the log message is saved into
760: * curlogmsg and the text is processed according to parameter func.
761: * Assumes the initial lexeme must be read in first.
762: * Does not advance nexttok after it is finished, except if delta==nil.
763: */
764: { struct hshentry * nextdelta;
765:
766: do {
767: rewriteflag = false;
768: nextlex();
769: if (!(nextdelta=getnum())) {
770: if(delta)
771: faterror("Can't find delta for revision %s", delta->num);
772: else return; /* no more delta text nodes */
773: }
774: if ( nextdelta->selector != DELETE) {
775: rewriteflag = true;
776: fprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
777: }
778: if (!getkey(Klog) || nexttok!=STRING)
779: serror("Missing log entry");
780: elsif (delta==nextdelta) {
781: savestring(curlogmsg,logsize);
782: delta->log=curlogmsg;
783: } else {readstring();
784: if (delta!=nil) delta->log="";
785: }
786: nextlex();
787: if (!getkey(Ktext) || nexttok!=STRING)
788: fatserror("Missing delta text");
789:
790: if(delta==nextdelta)
791: /* got the one we're looking for */
792: switch (func) {
793: case copy: copystring();
794: break;
795: case edit: editstring(nil);
796: break;
797: default: faterror("Wrong scanlogtext");
798: }
799: else readstring(); /* skip over it */
800:
801: } while (delta!=nextdelta);
802: }
803:
804:
805:
806: releaselst(sourcelst)
807: struct access * sourcelst;
808: /* Function: release the storages whose address are in sourcelst */
809:
810: {
811: struct access * pt;
812:
813: pt = sourcelst;
814: while(pt) {
815: free(pt);
816: pt = pt->nextaccess;
817: }
818: }
819:
820:
821:
822: struct Lockrev * rmnewlocklst(which)
823: struct Lockrev * which;
824: /* Function: remove lock to revision which->revno form newlocklst */
825:
826: {
827: struct Lockrev * pt, *pre;
828:
829: while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){
830: free(newlocklst);
831: newlocklst = newlocklst->nextrev;
832: }
833:
834: pt = pre = newlocklst;
835: while( pt ) {
836: if ( ! strcmp(pt->revno, which->revno) ) {
837: free(pt);
838: pt = pt->nextrev;
839: pre->nextrev = pt;
840: }
841: else {
842: pre = pt;
843: pt = pt->nextrev;
844: }
845: }
846: return pre;
847: }
848:
849:
850:
851: struct access * removeaccess( who, sourcelst,flag)
852: struct access * who, * sourcelst;
853: int flag;
854: /* Function: remove the accessor-- who from sourcelst */
855:
856: {
857: struct access *pt, *pre;
858:
859: pt = sourcelst;
860: while( pt && (! strcmp(who->login, pt->login) )) {
861: free(pt);
862: flag = false;
863: pt = pt->nextaccess;
864: }
865: pre = sourcelst = pt;
866: while( pt ) {
867: if ( ! strcmp(who->login, pt->login) ) {
868: free(pt);
869: flag = false;
870: pt = pt->nextaccess;
871: pre->nextaccess = pt;
872: }
873: else {
874: pre = pt;
875: pt = pt->nextaccess;
876: }
877: }
878: if ( flag ) warn("Can't remove a nonexisting accessor %s",who->login);
879: return sourcelst;
880: }
881:
882:
883:
884: int addnewaccess( who )
885: struct access * who;
886: /* Function: add new accessor-- who into AccessList */
887:
888: {
889: struct access *pt, *pre;
890:
891: pre = pt = AccessList;
892:
893: while( pt ) {
894: if ( strcmp( who->login, pt->login) ) {
895: pre = pt;
896: pt = pt->nextaccess;
897: }
898: else
899: return 0;
900: }
901: if ( pre == pt )
902: AccessList = who;
903: else
904: pre->nextaccess = who;
905: return 1;
906: }
907:
908:
909: sendmail(Delta, who)
910: char * Delta, *who;
911: /* Function: mail to who, informing him that his lock on delta was
912: * broken by caller. Ask first whether to go ahead. Return false on
913: * error or if user decides not to break the lock.
914: */
915: {
916: char * messagefile;
917: int old1, old2, c, response, exitstatus;
918: FILE * mailmess;
919:
920:
921: fprintf(stdout, "Revision %s is already locked by %s.\n", Delta, who);
922: fprintf(stdout, "Do you want to break the lock? [ny](n): ");
923: response=c=getchar();
924: while (!(c==EOF || c=='\n')) c=getchar();/*skip to end of line*/
925: if (c == EOF) {
926: clearerr(stdin);
927: c = 'n';
928: }
929: if (response=='\n'||response=='n'||response=='N') return false;
930:
931: /* go ahead with breaking */
932: messagefile=mktempfile("/tmp/", "RCSmailXXXXX");
933: if ( (mailmess = fopen(messagefile, "w")) == NULL) {
934: faterror("Can't open file %s", messagefile);
935: }
936:
937: fprintf(mailmess, "Subject: Broken lock on %s\n\n",RCSfilename);
938: fprintf(mailmess, "Your lock on revision %s of file %s\n",Delta, getfullRCSname());
939: fprintf(mailmess,"has been broken by %s for the following reason:\n",caller);
940: fputs("State the reason for breaking the lock:\n", stdout);
941: fputs("(terminate with ^D or single '.')\n>> ", stdout);
942:
943: old1 = '\n'; old2 = ' ';
944: for (; ;) {
945: c = getchar();
946: if ( c == EOF ) {
947: clearerr(stdin);
948: putc('\n',stdout);
949: fprintf(mailmess, "%c\n", old1);
950: break;
951: }
952: else if ( c == '\n' && old1 == '.' && old2 == '\n')
953: break;
954: else {
955: fputc( old1, mailmess);
956: old2 = old1; old1 = c;
957: if (c== '\n') fputs(">> ", stdout);
958: }
959: }
960: ffclose(mailmess);
961: #ifdef V4_2BSD
962: sprintf(command, "/usr/lib/sendmail %s < %s",who,messagefile);
963: #else
964: sprintf(command, "/etc/delivermail -w %s < %s",who,messagefile);
965: #endif
966: exitstatus = system(command);
967: unlink(messagefile);
968: return(exitstatus==EX_OK);
969: }
970:
971:
972:
973: struct hshentry * breaklock(who,delta)
974: char * who; struct hshentry * delta;
975: /* function: Finds the lock held by who on delta,
976: * removes it, and returns a pointer to the delta.
977: * delta may be nil; then the first lock held by who is chosen.
978: * Prints an error message and returns nil if there is no such lock or error.
979: */
980: {
981: register struct lock * next, * trail;
982: char * num;
983: struct lock dummy;
984: int whor, numr;
985:
986: num=(delta==nil)?nil:delta->num;
987: dummy.nextlock=next=Locks;
988: trail = &dummy;
989: while (next!=nil) {
990: numr = strcmp(num, next->delta->num);
991: if ((whor=strcmp(who,next->login))==0 &&
992: (num==nil || numr==0))
993: break; /* found a lock */
994: if (num!=nil && numr==0 && whor !=0) {
995: if (!sendmail( num, next->login)){
996: diagnose("%s still locked by %s",num,next->login);
997: return nil;
998: } else break; /* continue after loop */
999: }
1000: trail=next;
1001: next=next->nextlock;
1002: }
1003: if (next!=nil) {
1004: /*found one */
1005: diagnose("%s unlocked",next->delta->num);
1006: trail->nextlock=next->nextlock;
1007: next->delta->lockedby=nil;
1008: Locks=dummy.nextlock;
1009: return next->delta;
1010: } else {
1011: if (delta)
1012: error("no lock set by %s for revision %s", who, num);
1013: else
1014: error("no lock set by %s",who);
1015: return nil;
1016: }
1017: }
1018:
1019:
1020:
1021: struct hshentry *searchcutpt(object, length, store)
1022: char * object;
1023: int length;
1024: struct hshentry * * store;
1025: /* Function: Search store and return entry with number being object. */
1026: /* cuttail = nil, if the entry is Head; otherwise, cuttail */
1027: /* is the entry point to the one with number being object */
1028:
1029: {
1030: while( compartial( (*store++)->num, object, length) ) ;
1031: store--;
1032:
1033: if ( *store == Head)
1034: cuthead = nil;
1035: else
1036: cuthead = *(store -1);
1037: return *store;
1038: }
1039:
1040:
1041:
1042: int branchpoint(strt, tail)
1043: struct hshentry *strt, *tail;
1044: /* Function: check whether the deltas between strt and tail */
1045: /* are locked or branch point, return 1 if any is */
1046: /* locked or branch point; otherwise, return 0 and */
1047: /* mark DELETE on selector */
1048:
1049: {
1050: struct hshentry *pt;
1051: struct lock *lockpt;
1052: int flag;
1053:
1054:
1055: pt = strt;
1056: flag = false;
1057: while( pt != tail) {
1058: if ( pt->branches ){ /* a branch point */
1059: flag = true;
1060: error("Can't remove branch point %s", pt->num);
1061: }
1062: lockpt = Locks;
1063: while(lockpt && lockpt->delta != pt)
1064: lockpt = lockpt->nextlock;
1065: if ( lockpt ) {
1066: flag = true;
1067: error("Can't remove locked revision %s",pt->num);
1068: }
1069: pt = pt->next;
1070: }
1071:
1072: if ( ! flag ) {
1073: pt = strt;
1074: while( pt != tail ) {
1075: pt->selector = DELETE;
1076: diagnose("deleting revision %s ",pt->num);
1077: pt = pt->next;
1078: }
1079: }
1080: return flag;
1081: }
1082:
1083:
1084:
1085: removerevs()
1086: /* Function: get the revision range to be removed, and place the */
1087: /* first revision removed in delstrt, the revision before */
1088: /* delstrt in cuthead( nil, if delstrt is head), and the */
1089: /* revision after the last removed revision in cuttail(nil */
1090: /* if the last is a leaf */
1091:
1092: {
1093: struct hshentry *target, *target2, * temp, *searchcutpt();
1094: int length, flag;
1095:
1096: flag = false;
1097: if ( ! expandsym(delrev->strt, &numrev[0]) ) return 0;
1098: target = genrevs(&numrev[0], nil, nil, nil, gendeltas);
1099: if ( ! target ) return 0;
1100: if ( cmpnum(target->num, &numrev[0]) ) flag = true;
1101: length = countnumflds( &numrev[0] );
1102:
1103: if ( delrev->code == 0 ) { /* -o rev or -o branch */
1104: if ( length % 2)
1105: temp=searchcutpt(target->num,length+1,gendeltas);
1106: else if (flag) {
1107: error("Revision %s does not exist", &numrev[0]);
1108: return 0;
1109: }
1110: else
1111: temp = searchcutpt(&numrev[0],length,gendeltas);
1112: cuttail = target->next;
1113: if ( branchpoint(temp, cuttail) ) {
1114: cuttail = nil;
1115: return 0;
1116: }
1117: delstrt = temp; /* first revision to be removed */
1118: return 1;
1119: }
1120:
1121: if ( length % 2 ) { /* invalid branch after -o */
1122: error("Invalid branch range %s after -o", &numrev[0]);
1123: return 0;
1124: }
1125:
1126: if ( delrev->code == 1 ) { /* -o -rev */
1127: if ( length > 2 ) {
1128: temp = searchcutpt( target->num, length-1, gendeltas);
1129: cuttail = target->next;
1130: }
1131: else {
1132: temp = searchcutpt(target->num, length, gendeltas);
1133: cuttail = target;
1134: while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
1135: cuttail = cuttail->next;
1136: }
1137: if ( branchpoint(temp, cuttail) ){
1138: cuttail = nil;
1139: return 0;
1140: }
1141: delstrt = temp;
1142: return 1;
1143: }
1144:
1145: if ( delrev->code == 2 ) { /* -o rev- */
1146: if ( length == 2 ) {
1147: temp = searchcutpt(target->num, 1,gendeltas);
1148: if ( flag)
1149: cuttail = target;
1150: else
1151: cuttail = target->next;
1152: }
1153: else {
1154: if ( flag){
1155: cuthead = target;
1156: if ( !(temp = target->next) ) return 0;
1157: }
1158: else
1159: temp = searchcutpt(target->num, length, gendeltas);
1160: getbranchno(temp->num, &numrev[0]); /* get branch number */
1161: target = genrevs(&numrev[0], nil, nil, nil, gendeltas);
1162: }
1163: if ( branchpoint( temp, cuttail ) ) {
1164: cuttail = nil;
1165: return 0;
1166: }
1167: delstrt = temp;
1168: return 1;
1169: }
1170:
1171: /* -o rev1-rev2 */
1172: if ( ! expandsym(delrev->end, &numrev[0]) ) return 0;
1173: if ( length != countnumflds( &numrev[0] ) ) {
1174: error("Invalid revision range %s-%s", target->num, &numrev[0]);
1175: return 0;
1176: }
1177: if ( length > 2 && compartial( &numrev[0], target->num, length-1) ) {
1178: error("Invalid revision range %s-%s", target->num, &numrev[0]);
1179: return 0;
1180: }
1181:
1182: target2 = genrevs( &numrev[0], nil, nil, nil,gendeltas);
1183: if ( ! target2 ) return 0;
1184:
1185: if ( length > 2) { /* delete revisions on branches */
1186: if ( cmpnum(target->num, target2->num) > 0) {
1187: if ( cmpnum(target2->num, &numrev[0]) )
1188: flag = true;
1189: else
1190: flag = false;
1191: temp = target;
1192: target = target2;
1193: target2 = temp;
1194: }
1195: if ( flag ) {
1196: if ( ! cmpnum(target->num, target2->num) ) {
1197: error("Revisions %s-%s don't exist", delrev->strt,delrev->end);
1198: return 0;
1199: }
1200: cuthead = target;
1201: temp = target->next;
1202: }
1203: else
1204: temp = searchcutpt(target->num, length, gendeltas);
1205: cuttail = target2->next;
1206: }
1207: else { /* delete revisions on trunk */
1208: if ( cmpnum( target->num, target2->num) < 0 ) {
1209: temp = target;
1210: target = target2;
1211: target2 = temp;
1212: }
1213: else
1214: if ( cmpnum(target2->num, &numrev[0]) )
1215: flag = true;
1216: else
1217: flag = false;
1218: if ( flag ) {
1219: if ( ! cmpnum(target->num, target2->num) ) {
1220: error("Revisions %s-%s don't exist", delrev->strt, delrev->end);
1221: return 0;
1222: }
1223: cuttail = target2;
1224: }
1225: else
1226: cuttail = target2->next;
1227: temp = searchcutpt(target->num, length, gendeltas);
1228: }
1229: if ( branchpoint(temp, cuttail) ) {
1230: cuttail = nil;
1231: return 0;
1232: }
1233: delstrt = temp;
1234: return 1;
1235: }
1236:
1237:
1238:
1239: updateassoc()
1240: /* Function: add or delete(if revno is nil) association */
1241: /* which is stored in assoclst */
1242:
1243: {
1244: struct Symrev * curassoc;
1245: struct assoc * pre, * pt;
1246: struct hshentry * target;
1247:
1248: /* add new associations */
1249: curassoc = assoclst;
1250: while( curassoc ) {
1251: if ( curassoc->revno == nil ) { /* delete symbol */
1252: pre = pt = Symbols;
1253: while( pt && strcmp(pt->symbol,curassoc->ssymbol) ) {
1254: pre = pt;
1255: pt = pt->nextassoc;
1256: }
1257: if ( pt )
1258: if ( pre == pt )
1259: Symbols = pt->nextassoc;
1260: else
1261: pre->nextassoc = pt->nextassoc;
1262: else
1263: warn("Can't delete nonexisting symbol %s",curassoc->ssymbol);
1264: }
1265: else if ( expandsym( curassoc->revno, &numrev[0] ) ) {
1266: /* add symbol */
1267: target = (struct hshentry *) malloc(sizeof(struct hshentry));
1268: target->num = &numrev[0];
1269: addsymbol(target, curassoc->ssymbol, curassoc->override);
1270: }
1271: curassoc = curassoc->nextsym;
1272: }
1273:
1274: }
1275:
1276:
1277:
1278: updatelock()
1279: /* Function: remove locks which are stored in rmvlocklst, */
1280: /* add new locks which are stored in newlocklst, */
1281:
1282: {
1283: struct hshentry *target;
1284: struct Lockrev *lockpt;
1285: struct lock *lpt;
1286:
1287: /* remove locks which stored in rmvlocklst */
1288: lockpt = rmvlocklst;
1289: while( lockpt ) {
1290: if (expandsym(lockpt->revno, &numrev[0]) ) {
1291: target = genrevs(&numrev[0], nil, nil, nil, gendeltas);
1292: if ( target )
1293: if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num,&numrev[0]) )
1294: error("Can't unlock a nonexisting revision %s",lockpt->revno);
1295: else
1296: breaklock(caller, target);
1297: /* breaklock does it's own diagnose */
1298: }
1299: lockpt = lockpt->nextrev;
1300: }
1301:
1302: /* add new locks which stored in newlocklst */
1303: lockpt = newlocklst;
1304: while( lockpt ) {
1305: if (expandsym(lockpt->revno, &numrev[0]) ){
1306: target = genrevs(&numrev[0], nil, nil, nil, gendeltas);
1307: if ( target )
1308: if ( !(countnumflds(&numrev[0])%2) && cmpnum(target->num,&numrev[0]))
1309: error("Can't lock a nonexisting revision %s",lockpt->revno);
1310: else
1311: if(lpt=addlock(target, caller))
1312: diagnose("%s locked",lpt->delta->num);
1313: }
1314: lockpt = lockpt->nextrev;
1315: }
1316:
1317: }
1318:
1319:
1320:
1321: buildeltatext(deltas)
1322: struct hshentry ** deltas;
1323: /* Function: put the delta text on frewrite and make necessary */
1324: /* change to delta text */
1325: {
1326: int i, c, exit_stats;
1327:
1328: cuttail->selector = DELETE;
1329: initeditfiles("/tmp/");
1330: scanlogtext(deltas[0], copy);
1331: i = 1;
1332: if ( cuthead ) {
1333: cutfilename=mktempfile("/tmp/", "RCScutXXXXXX");
1334: if ( (fcut = fopen(cutfilename, "w")) == NULL) {
1335: faterror("Can't open temporary file %s", cutfilename);
1336: }
1337:
1338: while( deltas[i-1] != cuthead ) {
1339: scanlogtext(deltas[i++], edit);
1340: }
1341:
1342: finishedit(nil); rewind(fcopy);
1343: while( (c = getc(fcopy)) != EOF) putc(c, fcut);
1344: swapeditfiles(false);
1345: ffclose(fcut);
1346: }
1347:
1348: while( deltas[i-1] != cuttail)
1349: scanlogtext(deltas[i++], edit);
1350: finishedit(nil); ffclose(fcopy);
1351:
1352: if ( cuthead ) {
1353: diffilename=mktempfile("/tmp/", "RCSdifXXXXXX");
1354: sprintf(command, "%s -n %s %s > %s", DIFF,cutfilename, resultfile, diffilename);
1355: exit_stats = system (command);
1356: if (exit_stats != 0 && exit_stats != (1 << BYTESIZ))
1357: faterror ("diff failed");
1358: if(!putdtext(cuttail->num,curlogmsg,diffilename,frewrite)) return;
1359: }
1360: else
1361: if (!putdtext(cuttail->num,curlogmsg,resultfile,frewrite)) return;
1362:
1363: scanlogtext(nil,empty); /* read the rest of the deltas */
1364: }
1365:
1366:
1367:
1368: buildtree()
1369: /* Function: actually removes revisions whose selector field */
1370: /* is DELETE, and rebuilds the linkage of deltas. */
1371: /* asks for reconfirmation if deleting last revision*/
1372: {
1373: int c, response;
1374:
1375: struct hshentry * Delta;
1376: struct branchhead *pt, *pre;
1377:
1378: if ( cuthead )
1379: if ( cuthead->next == delstrt )
1380: cuthead->next = cuttail;
1381: else {
1382: pre = pt = cuthead->branches;
1383: while( pt && pt->hsh != delstrt ) {
1384: pre = pt;
1385: pt = pt->nextbranch;
1386: }
1387: if ( cuttail )
1388: pt->hsh = cuttail;
1389: else if ( pt == pre )
1390: cuthead->branches = pt->nextbranch;
1391: else
1392: pre->nextbranch = pt->nextbranch;
1393: }
1394: else {
1395: if ( cuttail == nil && !quietflag) {
1396: fprintf(stderr,"Do you really want to delete all revisions ?[ny](n): ");
1397: c = response = getchar();
1398: while( c != EOF && c != '\n') c = getchar();
1399: if (c == EOF)
1400: clearerr(stdin);
1401: if ( response != 'y' && response != 'Y') {
1402: diagnose("No revision deleted");
1403: Delta = delstrt;
1404: while( Delta) {
1405: Delta->selector = 'S';
1406: Delta = Delta->next;
1407: }
1408: return;
1409: }
1410: }
1411: Head = cuttail;
1412: }
1413: return;
1414: }