home *** CD-ROM | disk | FTP | other *** search
Text File | 1991-12-19 | 86.5 KB | 3,270 lines |
- Newsgroups: comp.sources.unix
- From: hammer@cs.purdue.edu (Adam Hammer)
- Subject: v25i084: rcs-5.6 - Revision Control System, V5.6, Part08/11
- Sender: sources-moderator@pa.dec.com
- Approved: vixie@pa.dec.com
-
- Submitted-By: hammer@cs.purdue.edu (Adam Hammer)
- Posting-Number: Volume 25, Issue 84
- Archive-Name: rcs-5.6/part08
-
- #! /bin/sh
- # This is a shell archive. Remove anything before this line, then unpack
- # it by saving it into a file and typing "sh file". To overwrite existing
- # files, type "sh file -c". You can also feed this as standard input via
- # unshar, or by typing "sh <file", e.g.. If this archive is complete, you
- # will see the following message at the end:
- # "End of archive 8 (of 11)."
- # Contents: src/rcs.c src/rcsedit.c
- # Wrapped by vixie@cognition.pa.dec.com on Fri Dec 20 16:23:41 1991
- PATH=/bin:/usr/bin:/usr/ucb ; export PATH
- if test -f 'src/rcs.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'src/rcs.c'\"
- else
- echo shar: Extracting \"'src/rcs.c'\" \(43795 characters\)
- sed "s/^X//" >'src/rcs.c' <<'END_OF_FILE'
- X/*
- X * RCS create/change operation
- X */
- X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
- X Copyright 1990, 1991 by Paul Eggert
- X Distributed under license by the Free Software Foundation, Inc.
- X
- This file is part of RCS.
- X
- RCS is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
- X
- RCS is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- X
- You should have received a copy of the GNU General Public License
- along with RCS; see the file COPYING. If not, write to
- the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
- X
- Report problems and direct all questions to:
- X
- X rcs-bugs@cs.purdue.edu
- X
- X*/
- X
- X
- X
- X
- X/* $Log: rcs.c,v $
- X * Revision 5.12 1991/11/20 17:58:08 eggert
- X * Don't read the delta tree from a nonexistent RCS file.
- X *
- X * Revision 5.11 1991/10/07 17:32:46 eggert
- X * Remove lint.
- X *
- X * Revision 5.10 1991/08/19 23:17:54 eggert
- X * Add -m, -r$, piece tables. Revision separator is `:', not `-'. Tune.
- X *
- X * Revision 5.9 1991/04/21 11:58:18 eggert
- X * Add -x, RCSINIT, MS-DOS support.
- X *
- X * Revision 5.8 1991/02/25 07:12:38 eggert
- X * strsave -> str_save (DG/UX name clash)
- X * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability
- X *
- X * Revision 5.7 1990/12/18 17:19:21 eggert
- X * Fix bug with multiple -n and -N options.
- X *
- X * Revision 5.6 1990/12/04 05:18:40 eggert
- X * Use -I for prompts and -q for diagnostics.
- X *
- X * Revision 5.5 1990/11/11 00:06:35 eggert
- X * Fix `rcs -e' core dump.
- X *
- X * Revision 5.4 1990/11/01 05:03:33 eggert
- X * Add -I and new -t behavior. Permit arbitrary data in logs.
- X *
- X * Revision 5.3 1990/10/04 06:30:16 eggert
- X * Accumulate exit status across files.
- X *
- X * Revision 5.2 1990/09/04 08:02:17 eggert
- X * Standardize yes-or-no procedure.
- X *
- X * Revision 5.1 1990/08/29 07:13:51 eggert
- X * Remove unused setuid support. Clean old log messages too.
- X *
- X * Revision 5.0 1990/08/22 08:12:42 eggert
- X * Don't lose names when applying -a option to multiple files.
- X * Remove compile-time limits; use malloc instead. Add setuid support.
- X * Permit dates past 1999/12/31. Make lock and temp files faster and safer.
- X * Ansify and Posixate. Add -V. Fix umask bug. Make linting easier. Tune.
- X * Yield proper exit status. Check diff's output.
- X *
- X * Revision 4.11 89/05/01 15:12:06 narten
- X * changed copyright header to reflect current distribution rules
- X *
- X * Revision 4.10 88/11/08 16:01:54 narten
- X * didn't install previous patch correctly
- X *
- X * Revision 4.9 88/11/08 13:56:01 narten
- X * removed include <sysexits.h> (not needed)
- X * minor fix for -A option
- X *
- X * Revision 4.8 88/08/09 19:12:27 eggert
- X * Don't access freed storage.
- X * Use execv(), not system(); yield proper exit status; remove lint.
- X *
- X * Revision 4.7 87/12/18 11:37:17 narten
- X * lint cleanups (Guy Harris)
- X *
- X * Revision 4.6 87/10/18 10:28:48 narten
- X * Updating verison numbers. Changes relative to 1.1 are actually
- X * relative to 4.3
- X *
- X * Revision 1.4 87/09/24 13:58:52 narten
- X * Sources now pass through lint (if you ignore printf/sprintf/fprintf
- X * warnings)
- X *
- X * Revision 1.3 87/03/27 14:21:55 jenkins
- X * Port to suns
- X *
- X * Revision 1.2 85/12/17 13:59:09 albitz
- X * Changed setstate to rcs_setstate because of conflict with random.o.
- X *
- X * Revision 4.3 83/12/15 12:27:33 wft
- X * rcs -u now breaks most recent lock if it can't find a lock by the caller.
- X *
- X * Revision 4.2 83/12/05 10:18:20 wft
- X * Added conditional compilation for sending mail.
- X * Alternatives: V4_2BSD, V6, USG, and other.
- X *
- X * Revision 4.1 83/05/10 16:43:02 wft
- X * Simplified breaklock(); added calls to findlock() and getcaller().
- X * Added option -b (default branch). Updated -s and -w for -b.
- X * Removed calls to stat(); now done by pairfilenames().
- X * Replaced most catchints() calls with restoreints().
- X * Removed check for exit status of delivermail().
- X * Directed all interactive output to stderr.
- X *
- X * Revision 3.9.1.1 83/12/02 22:08:51 wft
- X * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
- X *
- X * Revision 3.9 83/02/15 15:38:39 wft
- X * Added call to fastcopy() to copy remainder of RCS file.
- X *
- X * Revision 3.8 83/01/18 17:37:51 wft
- X * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
- X *
- X * Revision 3.7 83/01/15 18:04:25 wft
- X * Removed putree(); replaced with puttree() in rcssyn.c.
- X * Combined putdellog() and scanlogtext(); deleted putdellog().
- X * Cleaned up diagnostics and error messages. Fixed problem with
- X * mutilated files in case of deletions in 2 files in a single command.
- X * Changed marking of selector from 'D' to DELETE.
- X *
- X * Revision 3.6 83/01/14 15:37:31 wft
- X * Added ignoring of interrupts while new RCS file is renamed;
- X * Avoids deletion of RCS files by interrupts.
- X *
- X * Revision 3.5 82/12/10 21:11:39 wft
- X * Removed unused variables, fixed checking of return code from diff,
- X * introduced variant COMPAT2 for skipping Suffix on -A files.
- X *
- X * Revision 3.4 82/12/04 13:18:20 wft
- X * Replaced getdelta() with gettree(), changed breaklock to update
- X * field lockedby, added some diagnostics.
- X *
- X * Revision 3.3 82/12/03 17:08:04 wft
- X * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
- X * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
- X * fixed -u for missing revno. Disambiguated structure members.
- X *
- X * Revision 3.2 82/10/18 21:05:07 wft
- X * rcs -i now generates a file mode given by the umask minus write permission;
- X * otherwise, rcs keeps the mode, but removes write permission.
- X * I added a check for write error, fixed call to getlogin(), replaced
- X * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
- X * conflicting, long identifiers.
- X *
- X * Revision 3.1 82/10/13 16:11:07 wft
- X * fixed type of variables receiving from getc() (char -> int).
- X */
- X
- X
- X#include "rcsbase.h"
- X
- struct Lockrev {
- X char const *revno;
- X struct Lockrev * nextrev;
- X};
- X
- struct Symrev {
- X char const *revno;
- X char const *ssymbol;
- X int override;
- X struct Symrev * nextsym;
- X};
- X
- struct Message {
- X char const *revno;
- X struct cbuf message;
- X struct Message *nextmessage;
- X};
- X
- struct Status {
- X char const *revno;
- X char const *status;
- X struct Status * nextstatus;
- X};
- X
- enum changeaccess {append, erase};
- struct chaccess {
- X char const *login;
- X enum changeaccess command;
- X struct chaccess *nextchaccess;
- X};
- X
- struct delrevpair {
- X char const *strt;
- X char const *end;
- X int code;
- X};
- X
- static int buildeltatext P((struct hshentries const*));
- static int removerevs P((void));
- static int sendmail P((char const*,char const*));
- static struct Lockrev *rmnewlocklst P((struct Lockrev const*));
- static void breaklock P((struct hshentry const*));
- static void buildtree P((void));
- static void cleanup P((void));
- static void doaccess P((void));
- static void doassoc P((void));
- static void dolocks P((void));
- static void domessages P((void));
- static void getaccessor P((char*,enum changeaccess));
- static void getassoclst P((int,char*));
- static void getchaccess P((char const*,enum changeaccess));
- static void getdelrev P((char*));
- static void getmessage P((char*));
- static void getstates P((char*));
- static void rcs_setstate P((char const*,char const*));
- static void scanlogtext P((struct hshentry*,int));
- static void setlock P((char const*));
- X
- static struct buf numrev;
- static char const *headstate;
- static int chgheadstate, exitstatus, lockhead, unlockcaller;
- static struct Lockrev *newlocklst, *rmvlocklst;
- static struct Message *messagelst, *lastmessage;
- static struct Status *statelst, *laststate;
- static struct Symrev *assoclst, *lastassoc;
- static struct chaccess *chaccess, **nextchaccess;
- static struct delrevpair delrev;
- static struct hshentry *cuthead, *cuttail, *delstrt;
- static struct hshentries *gendeltas;
- X
- mainProg(rcsId, "rcs", "$Id: rcs.c,v 5.12 1991/11/20 17:58:08 eggert Exp $")
- X{
- X static char const cmdusage[] =
- X "\nrcs usage: rcs -{ae}logins -Afile -{blu}[rev] -cstring -{iLU} -{nNs}name[:rev] -orange -t[file] -Vn file ...";
- X
- X char *a, **newargv, *textfile;
- X char const *branchsym, *commsyml;
- X int branchflag, expmode, initflag;
- X int e, r, strictlock, strict_selected, textflag;
- X mode_t defaultRCSmode; /* default mode for new RCS files */
- X mode_t RCSmode;
- X struct buf branchnum;
- X struct stat workstat;
- X struct Lockrev *curlock, * rmvlock, *lockpt;
- X struct Status * curstate;
- X
- X nosetid();
- X
- X nextchaccess = &chaccess;
- X branchsym = commsyml = textfile = nil;
- X branchflag = strictlock = false;
- X bufautobegin(&branchnum);
- X curlock = rmvlock = nil;
- X defaultRCSmode = 0;
- X expmode = -1;
- X suffixes = X_DEFAULT;
- X initflag= textflag = false;
- X strict_selected = 0;
- X
- X /* preprocessing command options */
- X argc = getRCSINIT(argc, argv, &newargv);
- X argv = newargv;
- X while (a = *++argv, 0<--argc && *a++=='-') {
- X switch (*a++) {
- X
- X case 'i': /* initial version */
- X initflag = true;
- X break;
- X
- X case 'b': /* change default branch */
- X if (branchflag) redefined('b');
- X branchflag= true;
- X branchsym = a;
- X break;
- X
- X case 'c': /* change comment symbol */
- X if (commsyml) redefined('c');
- X commsyml = a;
- X break;
- X
- X case 'a': /* add new accessor */
- X getaccessor(*argv+1, append);
- X break;
- X
- X case 'A': /* append access list according to accessfile */
- X if (!*a) {
- X error("missing file name after -A");
- X break;
- X }
- X *argv = a;
- X if (0 < pairfilenames(1,argv,rcsreadopen,true,false)) {
- X while (AccessList) {
- X getchaccess(str_save(AccessList->login),append);
- X AccessList = AccessList->nextaccess;
- X }
- X Izclose(&finptr);
- X }
- X break;
- X
- X case 'e': /* remove accessors */
- X getaccessor(*argv+1, erase);
- X break;
- X
- X case 'l': /* lock a revision if it is unlocked */
- X if (!*a) {
- X /* Lock head or default branch. */
- X lockhead = true;
- X break;
- X }
- X lockpt = talloc(struct Lockrev);
- X lockpt->revno = a;
- X lockpt->nextrev = nil;
- X if ( curlock )
- X curlock->nextrev = lockpt;
- X else
- X newlocklst = lockpt;
- X curlock = lockpt;
- X break;
- X
- X case 'u': /* release lock of a locked revision */
- X if (!*a) {
- X unlockcaller=true;
- X break;
- X }
- X lockpt = talloc(struct Lockrev);
- X lockpt->revno = a;
- X lockpt->nextrev = nil;
- X if (rmvlock)
- X rmvlock->nextrev = lockpt;
- X else
- X rmvlocklst = lockpt;
- X rmvlock = lockpt;
- X
- X curlock = rmnewlocklst(lockpt);
- X break;
- X
- X case 'L': /* set strict locking */
- X if (strict_selected++) { /* Already selected L or U? */
- X if (!strictlock) /* Already selected -U? */
- X warn("-L overrides -U.");
- X }
- X strictlock = true;
- X break;
- X
- X case 'U': /* release strict locking */
- X if (strict_selected++) { /* Already selected L or U? */
- X if (strictlock) /* Already selected -L? */
- X warn("-L overrides -U.");
- X }
- X else
- X strictlock = false;
- X break;
- X
- X case 'n': /* add new association: error, if name exists */
- X if (!*a) {
- X error("missing symbolic name after -n");
- X break;
- X }
- X getassoclst(false, (*argv)+1);
- X break;
- X
- X case 'N': /* add or change association */
- X if (!*a) {
- X error("missing symbolic name after -N");
- X break;
- X }
- X getassoclst(true, (*argv)+1);
- X break;
- X
- X case 'm': /* change log message */
- X getmessage(a);
- X break;
- X
- X case 'o': /* delete revisions */
- X if (delrev.strt) redefined('o');
- X if (!*a) {
- X error("missing revision range after -o");
- X break;
- X }
- X getdelrev( (*argv)+1 );
- X break;
- X
- X case 's': /* change state attribute of a revision */
- X if (!*a) {
- X error("state missing after -s");
- X break;
- X }
- X getstates( (*argv)+1);
- X break;
- X
- X case 't': /* change descriptive text */
- X textflag=true;
- X if (*a) {
- X if (textfile) redefined('t');
- X textfile = a;
- X }
- X break;
- X
- X case 'I':
- X interactiveflag = true;
- X break;
- X
- X case 'q':
- X quietflag = true;
- X break;
- X
- X case 'x':
- X suffixes = a;
- X break;
- X
- X case 'V':
- X setRCSversion(*argv);
- X break;
- X
- X case 'k': /* set keyword expand mode */
- X if (0 <= expmode) redefined('k');
- X if (0 <= (expmode = str2expmode(a)))
- X break;
- X /* fall into */
- X default:
- X faterror("unknown option: %s%s", *argv, cmdusage);
- X };
- X } /* end processing of options */
- X
- X if (argc<1) faterror("no input file%s", cmdusage);
- X if (nerror) {
- X diagnose("%s aborted\n",cmdid);
- X exitmain(EXIT_FAILURE);
- X }
- X if (initflag) {
- X defaultRCSmode = umask((mode_t)0);
- X VOID umask(defaultRCSmode);
- X defaultRCSmode = (S_IRUSR|S_IRGRP|S_IROTH) & ~defaultRCSmode;
- X }
- X
- X /* now handle all filenames */
- X do {
- X ffree();
- X
- X if ( initflag ) {
- X switch (pairfilenames(argc, argv, rcswriteopen, false, false)) {
- X case -1: break; /* not exist; ok */
- X case 0: continue; /* error */
- X case 1: error("file %s exists already", RCSfilename);
- X continue;
- X }
- X }
- X else {
- X switch (pairfilenames(argc, argv, rcswriteopen, true, false)) {
- X case -1: continue; /* not exist */
- X case 0: continue; /* errors */
- X case 1: break; /* file exists; ok*/
- X }
- X }
- X
- X
- X /* now RCSfilename contains the name of the RCS file, and
- X * workfilename contains the name of the working file.
- X * if !initflag, finptr contains the file descriptor for the
- X * RCS file. The admin node is initialized.
- X */
- X
- X diagnose("RCS file: %s\n", RCSfilename);
- X
- X RCSmode = defaultRCSmode;
- X if (initflag) {
- X if (stat(workfilename, &workstat) == 0)
- X RCSmode = workstat.st_mode;
- X } else {
- X if (!checkaccesslist()) continue;
- X gettree(); /* Read the delta tree. */
- X RCSmode = RCSstat.st_mode;
- X }
- X RCSmode &= ~(S_IWUSR|S_IWGRP|S_IWOTH);
- X
- X /* update admin. node */
- X if (strict_selected) StrictLocks = strictlock;
- X if (commsyml) {
- X Comment.string = commsyml;
- X Comment.size = strlen(commsyml);
- X }
- X if (0 <= expmode) Expand = expmode;
- X
- X /* update default branch */
- X if (branchflag && expandsym(branchsym, &branchnum)) {
- X if (countnumflds(branchnum.string)) {
- X Dbranch = branchnum.string;
- X } else
- X Dbranch = nil;
- X }
- X
- X doaccess(); /* Update access list. */
- X
- X doassoc(); /* Update association list. */
- X
- X dolocks(); /* Update locks. */
- X
- X domessages(); /* Update log messages. */
- X
- X /* update state attribution */
- X if (chgheadstate) {
- X /* change state of default branch or head */
- X if (Dbranch==nil) {
- X if (Head==nil)
- X warn("can't change states in an empty tree");
- X else Head->state = headstate;
- X } else {
- X rcs_setstate(Dbranch,headstate); /* Can't set directly */
- X }
- X }
- X curstate = statelst;
- X while( curstate ) {
- X rcs_setstate(curstate->revno,curstate->status);
- X curstate = curstate->nextstatus;
- X }
- X
- X cuthead = cuttail = nil;
- X if (delrev.strt && removerevs()) {
- X /* rebuild delta tree if some deltas are deleted */
- X if ( cuttail )
- X VOID genrevs(cuttail->num, (char *)nil,(char *)nil,
- X (char *)nil, &gendeltas);
- X buildtree();
- X }
- X
- X if (nerror)
- X continue;
- X
- X putadmin(frewrite);
- X if ( Head )
- X puttree(Head, frewrite);
- X putdesc(textflag,textfile);
- X
- X if ( Head) {
- X if (!delrev.strt && !messagelst) {
- X /* No revision was deleted and no message was changed. */
- X fastcopy(finptr, frewrite);
- X } else {
- X if (!cuttail || buildeltatext(gendeltas)) {
- X advise_access(finptr, MADV_SEQUENTIAL);
- X scanlogtext((struct hshentry *)nil, false);
- X /* copy rest of delta text nodes that are not deleted */
- X }
- X }
- X }
- X Izclose(&finptr);
- X if ( ! nerror ) { /* move temporary file to RCS file if no error */
- X /* update mode */
- X ignoreints();
- X r = chnamemod(&frewrite, newRCSfilename, RCSfilename, RCSmode);
- X e = errno;
- X keepdirtemp(newRCSfilename);
- X restoreints();
- X if (r != 0) {
- X enerror(e, RCSfilename);
- X error("saved in %s", newRCSfilename);
- X dirtempunlink();
- X break;
- X }
- X diagnose("done\n");
- X } else {
- X diagnose("%s aborted; %s unchanged.\n",cmdid,RCSfilename);
- X }
- X } while (cleanup(),
- X ++argv, --argc >=1);
- X
- X tempunlink();
- X exitmain(exitstatus);
- X} /* end of main (rcs) */
- X
- X static void
- cleanup()
- X{
- X if (nerror) exitstatus = EXIT_FAILURE;
- X Izclose(&finptr);
- X Ozclose(&fcopy);
- X Ozclose(&frewrite);
- X dirtempunlink();
- X}
- X
- X exiting void
- exiterr()
- X{
- X dirtempunlink();
- X tempunlink();
- X _exit(EXIT_FAILURE);
- X}
- X
- X
- X static void
- getassoclst(flag, sp)
- int flag;
- char * sp;
- X/* Function: associate a symbolic name to a revision or branch, */
- X/* and store in assoclst */
- X
- X{
- X struct Symrev * pt;
- X char const *temp;
- X int c;
- X
- X while( (c=(*++sp)) == ' ' || c == '\t' || c =='\n') ;
- X temp = sp;
- X sp = checkid(sp, ':'); /* check for invalid symbolic name */
- X c = *sp; *sp = '\0';
- X while( c == ' ' || c == '\t' || c == '\n') c = *++sp;
- X
- X if ( c != ':' && c != '\0') {
- X error("invalid string %s after option -n or -N",sp);
- X return;
- X }
- X
- X pt = talloc(struct Symrev);
- X pt->ssymbol = temp;
- X pt->override = flag;
- X if (c == '\0') /* delete symbol */
- X pt->revno = nil;
- X else {
- X while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
- X pt->revno = sp;
- X }
- X pt->nextsym = nil;
- X if (lastassoc)
- X lastassoc->nextsym = pt;
- X else
- X assoclst = pt;
- X lastassoc = pt;
- X return;
- X}
- X
- X
- X static void
- getchaccess(login, command)
- X char const *login;
- X enum changeaccess command;
- X{
- X register struct chaccess *pt;
- X
- X *nextchaccess = pt = talloc(struct chaccess);
- X pt->login = login;
- X pt->command = command;
- X pt->nextchaccess = nil;
- X nextchaccess = &pt->nextchaccess;
- X}
- X
- X
- X
- X static void
- getaccessor(opt, command)
- X char *opt;
- X enum changeaccess command;
- X/* Function: get the accessor list of options -e and -a, */
- X/* and store in chaccess */
- X
- X
- X{
- X register c;
- X register char *sp;
- X
- X sp = opt;
- X while( ( c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') ;
- X if ( c == '\0') {
- X if (command == erase && sp-opt == 1) {
- X getchaccess((char const*)nil, command);
- X return;
- X }
- X error("missing login name after option -a or -e");
- X return;
- X }
- X
- X while( c != '\0') {
- X getchaccess(sp, command);
- X sp = checkid(sp,',');
- X c = *sp; *sp = '\0';
- X while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
- X }
- X}
- X
- X
- X static void
- getmessage(option)
- X char *option;
- X{
- X struct Message *pt;
- X struct cbuf cb;
- X char *m;
- X
- X if (!(m = strchr(option, ':'))) {
- X error("-m option lacks revision number");
- X return;
- X }
- X *m++ = 0;
- X cb = cleanlogmsg(m, strlen(m));
- X if (!cb.size) {
- X error("-m option lacks log message");
- X return;
- X }
- X pt = talloc(struct Message);
- X pt->revno = option;
- X pt->message = cb;
- X pt->nextmessage = 0;
- X if (lastmessage)
- X lastmessage->nextmessage = pt;
- X else
- X messagelst = pt;
- X lastmessage = pt;
- X}
- X
- X
- X static void
- getstates(sp)
- char *sp;
- X/* Function: get one state attribute and the corresponding */
- X/* revision and store in statelst */
- X
- X{
- X char const *temp;
- X struct Status *pt;
- X register c;
- X
- X while( (c=(*++sp)) ==' ' || c == '\t' || c == '\n') ;
- X temp = sp;
- X sp = checkid(sp,':'); /* check for invalid state attribute */
- X c = *sp; *sp = '\0';
- X while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp;
- X
- X if ( c == '\0' ) { /* change state of def. branch or Head */
- X chgheadstate = true;
- X headstate = temp;
- X return;
- X }
- X else if ( c != ':' ) {
- X error("missing ':' after state in option -s");
- X return;
- X }
- X
- X while( (c = *++sp) == ' ' || c == '\t' || c == '\n') ;
- X pt = talloc(struct Status);
- X pt->status = temp;
- X pt->revno = sp;
- X pt->nextstatus = nil;
- X if (laststate)
- X laststate->nextstatus = pt;
- X else
- X statelst = pt;
- X laststate = pt;
- X}
- X
- X
- X
- X static void
- getdelrev(sp)
- char *sp;
- X/* Function: get revision range or branch to be deleted, */
- X/* and place in delrev */
- X{
- X int c;
- X struct delrevpair *pt;
- X int separator;
- X
- X pt = &delrev;
- X while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
- X
- X /* Support old ambiguous '-' syntax; this will go away. */
- X if (strchr(sp,':'))
- X separator = ':';
- X else {
- X if (strchr(sp,'-') && VERSION(5) <= RCSversion)
- X warn("`-' is obsolete in `-o%s'; use `:' instead", sp);
- X separator = '-';
- X }
- X
- X if (c == separator) { /* -o:rev */
- X while( (c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
- X pt->strt = sp; pt->code = 1;
- X while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
- X *sp = '\0';
- X pt->end = nil;
- X return;
- X }
- X else {
- X pt->strt = sp;
- X while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
- X && c != separator ) c = *++sp;
- X *sp = '\0';
- X while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp;
- X if ( c == '\0' ) { /* -o rev or branch */
- X pt->end = nil; pt->code = 0;
- X return;
- X }
- X if (c != separator) {
- X faterror("invalid range %s %s after -o", pt->strt, sp);
- X }
- X while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
- X if (!c) { /* -orev: */
- X pt->end = nil; pt->code = 2;
- X return;
- X }
- X }
- X /* -orev1:rev2 */
- X pt->end = sp; pt->code = 3;
- X while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
- X *sp = '\0';
- X}
- X
- X
- X
- X
- X static void
- scanlogtext(delta,edit)
- X struct hshentry *delta;
- X int edit;
- X/* Function: Scans delta text nodes up to and including the one given
- X * by delta, or up to last one present, if delta==nil.
- X * For the one given by delta (if delta!=nil), the log message is saved into
- X * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied.
- X * Assumes the initial lexeme must be read in first.
- X * Does not advance nexttok after it is finished, except if delta==nil.
- X */
- X{
- X struct hshentry const *nextdelta;
- X struct cbuf cb;
- X
- X for (;;) {
- X foutptr = 0;
- X if (eoflex()) {
- X if(delta)
- X faterror("can't find delta for revision %s", delta->num);
- X return; /* no more delta text nodes */
- X }
- X nextlex();
- X if (!(nextdelta=getnum()))
- X faterror("delta number corrupted");
- X if (nextdelta->selector) {
- X foutptr = frewrite;
- X aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
- X }
- X getkeystring(Klog);
- X if (nextdelta == cuttail) {
- X cb = savestring(&curlogbuf);
- X if (!delta->log.string)
- X delta->log = cleanlogmsg(curlogbuf.string, cb.size);
- X } else if (nextdelta->log.string && nextdelta->selector) {
- X foutptr = 0;
- X readstring();
- X foutptr = frewrite;
- X putstring(foutptr, false, nextdelta->log, true);
- X afputc(nextc, foutptr);
- X } else {readstring();
- X }
- X nextlex();
- X while (nexttok==ID && strcmp(NextString,Ktext)!=0)
- X ignorephrase();
- X getkeystring(Ktext);
- X
- X if (delta==nextdelta)
- X break;
- X readstring(); /* skip over it */
- X
- X }
- X /* got the one we're looking for */
- X if (edit)
- X editstring((struct hshentry *)nil);
- X else
- X enterstring();
- X}
- X
- X
- X
- X static struct Lockrev *
- rmnewlocklst(which)
- X struct Lockrev const *which;
- X/* Function: remove lock to revision which->revno from newlocklst */
- X
- X{
- X struct Lockrev * pt, *pre;
- X
- X while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){
- X struct Lockrev *pn = newlocklst->nextrev;
- X tfree(newlocklst);
- X newlocklst = pn;
- X }
- X
- X pt = pre = newlocklst;
- X while( pt ) {
- X if ( ! strcmp(pt->revno, which->revno) ) {
- X pre->nextrev = pt->nextrev;
- X tfree(pt);
- X pt = pre->nextrev;
- X }
- X else {
- X pre = pt;
- X pt = pt->nextrev;
- X }
- X }
- X return pre;
- X}
- X
- X
- X
- X static void
- doaccess()
- X{
- X register struct chaccess *ch;
- X register struct access **p, *t;
- X
- X for (ch = chaccess; ch; ch = ch->nextchaccess) {
- X switch (ch->command) {
- X case erase:
- X if (!ch->login)
- X AccessList = nil;
- X else
- X for (p = &AccessList; (t = *p); )
- X if (strcmp(ch->login, t->login) == 0)
- X *p = t->nextaccess;
- X else
- X p = &t->nextaccess;
- X break;
- X case append:
- X for (p = &AccessList; ; p = &t->nextaccess)
- X if (!(t = *p)) {
- X *p = t = ftalloc(struct access);
- X t->login = ch->login;
- X t->nextaccess = nil;
- X break;
- X } else if (strcmp(ch->login, t->login) == 0)
- X break;
- X break;
- X }
- X }
- X}
- X
- X
- X static int
- sendmail(Delta, who)
- X char const *Delta, *who;
- X/* Function: mail to who, informing him that his lock on delta was
- X * broken by caller. Ask first whether to go ahead. Return false on
- X * error or if user decides not to break the lock.
- X */
- X{
- X#ifdef SENDMAIL
- X char const *messagefile;
- X int old1, old2, c;
- X FILE * mailmess;
- X#endif
- X
- X
- X aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
- X if (!yesorno(false, "Do you want to break the lock? [ny](n): "))
- X return false;
- X
- X /* go ahead with breaking */
- X#ifdef SENDMAIL
- X messagefile = maketemp(0);
- X if (!(mailmess = fopen(messagefile, "w"))) {
- X efaterror(messagefile);
- X }
- X
- X aprintf(mailmess, "Subject: Broken lock on %s\n\nYour lock on revision %s of file %s\nhas been broken by %s for the following reason:\n",
- X basename(RCSfilename), Delta, getfullRCSname(), getcaller()
- X );
- X aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr);
- X
- X old1 = '\n'; old2 = ' ';
- X for (; ;) {
- X c = getcstdin();
- X if (feof(stdin)) {
- X aprintf(mailmess, "%c\n", old1);
- X break;
- X }
- X else if ( c == '\n' && old1 == '.' && old2 == '\n')
- X break;
- X else {
- X afputc(old1, mailmess);
- X old2 = old1; old1 = c;
- X if (c=='\n') aputs(">> ", stderr);
- X }
- X }
- X Ozclose(&mailmess);
- X
- X if (run(messagefile, (char*)nil, SENDMAIL, who, (char*)nil))
- X warn("Mail may have failed."),
- X#else
- X warn("Mail notification of broken locks is not available."),
- X#endif
- X warn("Please tell `%s' why you broke the lock.", who);
- X return(true);
- X}
- X
- X
- X
- X static void
- breaklock(delta)
- X struct hshentry const *delta;
- X/* function: Finds the lock held by caller on delta,
- X * and removes it.
- X * Sends mail if a lock different from the caller's is broken.
- X * Prints an error message if there is no such lock or error.
- X */
- X{
- X register struct lock * next, * trail;
- X char const *num;
- X struct lock dummy;
- X
- X num=delta->num;
- X dummy.nextlock=next=Locks;
- X trail = &dummy;
- X while (next!=nil) {
- X if (strcmp(num, next->delta->num) == 0) {
- X if (
- X strcmp(getcaller(),next->login) != 0
- X && !sendmail(num, next->login)
- X ) {
- X error("%s still locked by %s", num, next->login);
- X return;
- X }
- X break; /* exact match */
- X }
- X trail=next;
- X next=next->nextlock;
- X }
- X if (next!=nil) {
- X /*found one */
- X diagnose("%s unlocked\n",next->delta->num);
- X trail->nextlock=next->nextlock;
- X next->delta->lockedby=nil;
- X Locks=dummy.nextlock;
- X } else {
- X error("no lock set on revision %s", num);
- X }
- X}
- X
- X
- X
- X static struct hshentry *
- searchcutpt(object, length, store)
- X char const *object;
- X unsigned length;
- X struct hshentries *store;
- X/* Function: Search store and return entry with number being object. */
- X/* cuttail = nil, if the entry is Head; otherwise, cuttail */
- X/* is the entry point to the one with number being object */
- X
- X{
- X cuthead = nil;
- X while (compartial(store->first->num, object, length)) {
- X cuthead = store->first;
- X store = store->rest;
- X }
- X return store->first;
- X}
- X
- X
- X
- X static int
- branchpoint(strt, tail)
- struct hshentry *strt, *tail;
- X/* Function: check whether the deltas between strt and tail */
- X/* are locked or branch point, return 1 if any is */
- X/* locked or branch point; otherwise, return 0 and */
- X/* mark deleted */
- X
- X{
- X struct hshentry *pt;
- X struct lock const *lockpt;
- X int flag;
- X
- X
- X pt = strt;
- X flag = false;
- X while( pt != tail) {
- X if ( pt->branches ){ /* a branch point */
- X flag = true;
- X error("can't remove branch point %s", pt->num);
- X }
- X lockpt = Locks;
- X while(lockpt && lockpt->delta != pt)
- X lockpt = lockpt->nextlock;
- X if ( lockpt ) {
- X flag = true;
- X error("can't remove locked revision %s",pt->num);
- X }
- X pt = pt->next;
- X }
- X
- X if ( ! flag ) {
- X pt = strt;
- X while( pt != tail ) {
- X pt->selector = false;
- X diagnose("deleting revision %s\n",pt->num);
- X pt = pt->next;
- X }
- X }
- X return flag;
- X}
- X
- X
- X
- X static int
- removerevs()
- X/* Function: get the revision range to be removed, and place the */
- X/* first revision removed in delstrt, the revision before */
- X/* delstrt in cuthead( nil, if delstrt is head), and the */
- X/* revision after the last removed revision in cuttail(nil */
- X/* if the last is a leaf */
- X
- X{
- X struct hshentry *target, *target2, *temp;
- X unsigned length;
- X int flag;
- X
- X flag = false;
- X if (!expandsym(delrev.strt, &numrev)) return 0;
- X target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas);
- X if ( ! target ) return 0;
- X if (cmpnum(target->num, numrev.string)) flag = true;
- X length = countnumflds(numrev.string);
- X
- X if (delrev.code == 0) { /* -o rev or -o branch */
- X if (length & 1)
- X temp=searchcutpt(target->num,length+1,gendeltas);
- X else if (flag) {
- X error("Revision %s doesn't exist.", numrev.string);
- X return 0;
- X }
- X else
- X temp = searchcutpt(numrev.string, length, gendeltas);
- X cuttail = target->next;
- X if ( branchpoint(temp, cuttail) ) {
- X cuttail = nil;
- X return 0;
- X }
- X delstrt = temp; /* first revision to be removed */
- X return 1;
- X }
- X
- X if (length & 1) { /* invalid branch after -o */
- X error("invalid branch range %s after -o", numrev.string);
- X return 0;
- X }
- X
- X if (delrev.code == 1) { /* -o -rev */
- X if ( length > 2 ) {
- X temp = searchcutpt( target->num, length-1, gendeltas);
- X cuttail = target->next;
- X }
- X else {
- X temp = searchcutpt(target->num, length, gendeltas);
- X cuttail = target;
- X while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
- X cuttail = cuttail->next;
- X }
- X if ( branchpoint(temp, cuttail) ){
- X cuttail = nil;
- X return 0;
- X }
- X delstrt = temp;
- X return 1;
- X }
- X
- X if (delrev.code == 2) { /* -o rev- */
- X if ( length == 2 ) {
- X temp = searchcutpt(target->num, 1,gendeltas);
- X if ( flag)
- X cuttail = target;
- X else
- X cuttail = target->next;
- X }
- X else {
- X if ( flag){
- X cuthead = target;
- X if ( !(temp = target->next) ) return 0;
- X }
- X else
- X temp = searchcutpt(target->num, length, gendeltas);
- X getbranchno(temp->num, &numrev); /* get branch number */
- X target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas);
- X }
- X if ( branchpoint( temp, cuttail ) ) {
- X cuttail = nil;
- X return 0;
- X }
- X delstrt = temp;
- X return 1;
- X }
- X
- X /* -o rev1-rev2 */
- X if (!expandsym(delrev.end, &numrev)) return 0;
- X if (
- X length != countnumflds(numrev.string)
- X || length>2 && compartial(numrev.string, target->num, length-1)
- X ) {
- X error("invalid revision range %s-%s", target->num, numrev.string);
- X return 0;
- X }
- X
- X target2 = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas);
- X if ( ! target2 ) return 0;
- X
- X if ( length > 2) { /* delete revisions on branches */
- X if ( cmpnum(target->num, target2->num) > 0) {
- X if (cmpnum(target2->num, numrev.string))
- X flag = true;
- X else
- X flag = false;
- X temp = target;
- X target = target2;
- X target2 = temp;
- X }
- X if ( flag ) {
- X if ( ! cmpnum(target->num, target2->num) ) {
- X error("Revisions %s-%s don't exist.", delrev.strt,delrev.end);
- X return 0;
- X }
- X cuthead = target;
- X temp = target->next;
- X }
- X else
- X temp = searchcutpt(target->num, length, gendeltas);
- X cuttail = target2->next;
- X }
- X else { /* delete revisions on trunk */
- X if ( cmpnum( target->num, target2->num) < 0 ) {
- X temp = target;
- X target = target2;
- X target2 = temp;
- X }
- X else
- X if (cmpnum(target2->num, numrev.string))
- X flag = true;
- X else
- X flag = false;
- X if ( flag ) {
- X if ( ! cmpnum(target->num, target2->num) ) {
- X error("Revisions %s-%s don't exist.", delrev.strt, delrev.end);
- X return 0;
- X }
- X cuttail = target2;
- X }
- X else
- X cuttail = target2->next;
- X temp = searchcutpt(target->num, length, gendeltas);
- X }
- X if ( branchpoint(temp, cuttail) ) {
- X cuttail = nil;
- X return 0;
- X }
- X delstrt = temp;
- X return 1;
- X}
- X
- X
- X
- X static void
- doassoc()
- X/* Function: add or delete(if revno is nil) association */
- X/* which is stored in assoclst */
- X
- X{
- X char const *p;
- X struct Symrev const *curassoc;
- X struct assoc * pre, * pt;
- X
- X /* add new associations */
- X curassoc = assoclst;
- X while( curassoc ) {
- X if ( curassoc->revno == nil ) { /* delete symbol */
- X pre = pt = Symbols;
- X while( pt && strcmp(pt->symbol,curassoc->ssymbol) ) {
- X pre = pt;
- X pt = pt->nextassoc;
- X }
- X if ( pt )
- X if ( pre == pt )
- X Symbols = pt->nextassoc;
- X else
- X pre->nextassoc = pt->nextassoc;
- X else
- X warn("can't delete nonexisting symbol %s",curassoc->ssymbol);
- X }
- X else {
- X if (curassoc->revno[0]) {
- X p = 0;
- X if (expandsym(curassoc->revno, &numrev))
- X p = fstr_save(numrev.string);
- X } else if (!(p = tiprev()))
- X error("no latest revision to associate with symbol %s",
- X curassoc->ssymbol
- X );
- X if (p)
- X VOID addsymbol(p, curassoc->ssymbol, curassoc->override);
- X }
- X curassoc = curassoc->nextsym;
- X }
- X
- X}
- X
- X
- X
- X static void
- dolocks()
- X/* Function: remove lock for caller or first lock if unlockcaller is set;
- X * remove locks which are stored in rmvlocklst,
- X * add new locks which are stored in newlocklst,
- X * add lock for Dbranch or Head if lockhead is set.
- X */
- X{
- X struct Lockrev const *lockpt;
- X struct hshentry *target;
- X
- X if (unlockcaller) { /* find lock for caller */
- X if ( Head ) {
- X if (Locks) {
- X switch (findlock(true, &target)) {
- X case 0:
- X breaklock(Locks->delta); /* remove most recent lock */
- X break;
- X case 1:
- X diagnose("%s unlocked\n",target->num);
- X break;
- X }
- X } else {
- X warn("No locks are set.");
- X }
- X } else {
- X warn("can't unlock an empty tree");
- X }
- X }
- X
- X /* remove locks which are stored in rmvlocklst */
- X lockpt = rmvlocklst;
- X while( lockpt ) {
- X if (expandsym(lockpt->revno, &numrev)) {
- X target = genrevs(numrev.string, (char *)nil, (char *)nil, (char *)nil, &gendeltas);
- X if ( target )
- X if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
- X error("can't unlock nonexisting revision %s",lockpt->revno);
- X else
- X breaklock(target);
- X /* breaklock does its own diagnose */
- X }
- X lockpt = lockpt->nextrev;
- X }
- X
- X /* add new locks which stored in newlocklst */
- X lockpt = newlocklst;
- X while( lockpt ) {
- X setlock(lockpt->revno);
- X lockpt = lockpt->nextrev;
- X }
- X
- X if (lockhead) { /* lock default branch or head */
- X if (Dbranch) {
- X setlock(Dbranch);
- X } else if (Head) {
- X if (0 <= addlock(Head))
- X diagnose("%s locked\n",Head->num);
- X } else {
- X warn("can't lock an empty tree");
- X }
- X }
- X
- X}
- X
- X
- X
- X static void
- setlock(rev)
- X char const *rev;
- X/* Function: Given a revision or branch number, finds the corresponding
- X * delta and locks it for caller.
- X */
- X{
- X struct hshentry *target;
- X
- X if (expandsym(rev, &numrev)) {
- X target = genrevs(numrev.string, (char*)nil, (char*)nil,
- X (char*)nil, &gendeltas);
- X if ( target )
- X if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
- X error("can't lock nonexisting revision %s", numrev.string);
- X else
- X if (0 <= addlock(target))
- X diagnose("%s locked\n", target->num);
- X }
- X}
- X
- X
- X static void
- domessages()
- X{
- X struct hshentry *target;
- X struct Message *p;
- X
- X for (p = messagelst; p; p = p->nextmessage)
- X if (
- X expandsym(p->revno, &numrev) &&
- X (target = genrevs(
- X numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas
- X ))
- X )
- X target->log = p->message;
- X}
- X
- X
- X static void
- rcs_setstate(rev,status)
- X char const *rev, *status;
- X/* Function: Given a revision or branch number, finds the corresponding delta
- X * and sets its state to status.
- X */
- X{
- X struct hshentry *target;
- X
- X if (expandsym(rev, &numrev)) {
- X target = genrevs(numrev.string, (char*)nil, (char*)nil,
- X (char*)nil, &gendeltas);
- X if ( target )
- X if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
- X error("can't set state of nonexisting revision %s to %s",
- X numrev.string, status);
- X else
- X target->state = status;
- X }
- X}
- X
- X
- X
- X
- X
- X static int
- buildeltatext(deltas)
- X struct hshentries const *deltas;
- X/* Function: put the delta text on frewrite and make necessary */
- X/* change to delta text */
- X{
- X register FILE *fcut; /* temporary file to rebuild delta tree */
- X char const *cutfilename, *diffilename;
- X
- X cutfilename = nil;
- X cuttail->selector = false;
- X scanlogtext(deltas->first, false);
- X if ( cuthead ) {
- X cutfilename = maketemp(3);
- X if (!(fcut = fopen(cutfilename, FOPEN_W_WORK))) {
- X efaterror(cutfilename);
- X }
- X
- X while (deltas->first != cuthead) {
- X deltas = deltas->rest;
- X scanlogtext(deltas->first, true);
- X }
- X
- X snapshotedit(fcut);
- X Ofclose(fcut);
- X }
- X
- X while (deltas->first != cuttail)
- X scanlogtext((deltas = deltas->rest)->first, true);
- X finishedit((struct hshentry *)nil, (FILE*)0, true);
- X Ozclose(&fcopy);
- X
- X if ( cuthead ) {
- X diffilename = maketemp(0);
- X switch (run((char*)nil,diffilename,
- X DIFF DIFF_FLAGS, cutfilename, resultfile, (char*)nil
- X )) {
- X case DIFF_FAILURE: case DIFF_SUCCESS: break;
- X default: faterror ("diff failed");
- X }
- X return putdtext(cuttail->num,cuttail->log,diffilename,frewrite,true);
- X } else
- X return putdtext(cuttail->num,cuttail->log,resultfile,frewrite,false);
- X}
- X
- X
- X
- X static void
- buildtree()
- X/* Function: actually removes revisions whose selector field */
- X/* is false, and rebuilds the linkage of deltas. */
- X/* asks for reconfirmation if deleting last revision*/
- X{
- X struct hshentry * Delta;
- X struct branchhead *pt, *pre;
- X
- X if ( cuthead )
- X if ( cuthead->next == delstrt )
- X cuthead->next = cuttail;
- X else {
- X pre = pt = cuthead->branches;
- X while( pt && pt->hsh != delstrt ) {
- X pre = pt;
- X pt = pt->nextbranch;
- X }
- X if ( cuttail )
- X pt->hsh = cuttail;
- X else if ( pt == pre )
- X cuthead->branches = pt->nextbranch;
- X else
- X pre->nextbranch = pt->nextbranch;
- X }
- X else {
- X if ( cuttail == nil && !quietflag) {
- X if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) {
- X error("No revision deleted");
- X Delta = delstrt;
- X while( Delta) {
- X Delta->selector = true;
- X Delta = Delta->next;
- X }
- X return;
- X }
- X }
- X Head = cuttail;
- X }
- X return;
- X}
- X
- X#if lint
- X/* This lets us lint everything all at once. */
- X
- char const cmdid[] = "";
- X
- X#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();}
- X
- X int
- main(argc, argv)
- X int argc;
- X char **argv;
- X{
- X go(ciId, ciExit);
- X go(coId, coExit);
- X go(identId, identExit);
- X go(mergeId, mergeExit);
- X go(rcsId, exiterr);
- X go(rcscleanId, rcscleanExit);
- X go(rcsdiffId, rdiffExit);
- X go(rcsmergeId, rmergeExit);
- X go(rlogId, rlogExit);
- X return 0;
- X}
- X#endif
- END_OF_FILE
- if test 43795 -ne `wc -c <'src/rcs.c'`; then
- echo shar: \"'src/rcs.c'\" unpacked with wrong size!
- fi
- # end of 'src/rcs.c'
- fi
- if test -f 'src/rcsedit.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'src/rcsedit.c'\"
- else
- echo shar: Extracting \"'src/rcsedit.c'\" \(39683 characters\)
- sed "s/^X//" >'src/rcsedit.c' <<'END_OF_FILE'
- X/*
- X * RCS stream editor
- X */
- X/**********************************************************************************
- X * edits the input file according to a
- X * script from stdin, generated by diff -n
- X * performs keyword expansion
- X **********************************************************************************
- X */
- X
- X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
- X Copyright 1990, 1991 by Paul Eggert
- X Distributed under license by the Free Software Foundation, Inc.
- X
- This file is part of RCS.
- X
- RCS is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
- X
- RCS is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- X
- You should have received a copy of the GNU General Public License
- along with RCS; see the file COPYING. If not, write to
- the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
- X
- Report problems and direct all questions to:
- X
- X rcs-bugs@cs.purdue.edu
- X
- X*/
- X
- X
- X/* $Log: rcsedit.c,v $
- X * Revision 5.11 1991/11/03 01:11:44 eggert
- X * Move the warning about link breaking to where they're actually being broken.
- X *
- X * Revision 5.10 1991/10/07 17:32:46 eggert
- X * Support piece tables even if !has_mmap. Fix rare NFS bugs.
- X *
- X * Revision 5.9 1991/09/17 19:07:40 eggert
- X * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
- X *
- X * Revision 5.8 1991/08/19 03:13:55 eggert
- X * Add piece tables, NFS bug workarounds. Catch odd filenames. Tune.
- X *
- X * Revision 5.7 1991/04/21 11:58:21 eggert
- X * Fix errno bugs. Add -x, RCSINIT, MS-DOS support.
- X *
- X * Revision 5.6 1991/02/25 07:12:40 eggert
- X * Fix setuid bug. Support new link behavior. Work around broken "w+" fopen.
- X *
- X * Revision 5.5 1990/12/30 05:07:35 eggert
- X * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL).
- X *
- X * Revision 5.4 1990/11/01 05:03:40 eggert
- X * Permit arbitrary data in comment leaders.
- X *
- X * Revision 5.3 1990/09/11 02:41:13 eggert
- X * Tune expandline().
- X *
- X * Revision 5.2 1990/09/04 08:02:21 eggert
- X * Count RCS lines better. Improve incomplete line handling.
- X *
- X * Revision 5.1 1990/08/29 07:13:56 eggert
- X * Add -kkvl.
- X * Fix bug when getting revisions to files ending in incomplete lines.
- X * Fix bug in comment leader expansion.
- X *
- X * Revision 5.0 1990/08/22 08:12:47 eggert
- X * Don't require final newline.
- X * Don't append "checked in with -k by " to logs,
- X * so that checking in a program with -k doesn't change it.
- X * Don't generate trailing white space for empty comment leader.
- X * Remove compile-time limits; use malloc instead. Add -k, -V.
- X * Permit dates past 1999/12/31. Make lock and temp files faster and safer.
- X * Ansify and Posixate. Check diff's output.
- X *
- X * Revision 4.8 89/05/01 15:12:35 narten
- X * changed copyright header to reflect current distribution rules
- X *
- X * Revision 4.7 88/11/08 13:54:14 narten
- X * misplaced semicolon caused infinite loop
- X *
- X * Revision 4.6 88/08/09 19:12:45 eggert
- X * Shrink stdio code size; allow cc -R.
- X *
- X * Revision 4.5 87/12/18 11:38:46 narten
- X * Changes from the 43. version. Don't know the significance of the
- X * first change involving "rewind". Also, additional "lint" cleanup.
- X * (Guy Harris)
- X *
- X * Revision 4.4 87/10/18 10:32:21 narten
- X * Updating version numbers. Changes relative to version 1.1 actually
- X * relative to 4.1
- X *
- X * Revision 1.4 87/09/24 13:59:29 narten
- X * Sources now pass through lint (if you ignore printf/sprintf/fprintf
- X * warnings)
- X *
- X * Revision 1.3 87/09/15 16:39:39 shepler
- X * added an initializatin of the variables editline and linecorr
- X * this will be done each time a file is processed.
- X * (there was an obscure bug where if co was used to retrieve multiple files
- X * it would dump)
- X * fix attributed to Roy Morris @FileNet Corp ...!felix!roy
- X *
- X * Revision 1.2 87/03/27 14:22:17 jenkins
- X * Port to suns
- X *
- X * Revision 4.1 83/05/12 13:10:30 wft
- X * Added new markers Id and RCSfile; added locker to Header and Id.
- X * Overhauled expandline completely() (problem with $01234567890123456789@).
- X * Moved trymatch() and marker table to rcskeys.c.
- X *
- X * Revision 3.7 83/05/12 13:04:39 wft
- X * Added retry to expandline to resume after failed match which ended in $.
- X * Fixed truncation problem for $19chars followed by@@.
- X * Log no longer expands full path of RCS file.
- X *
- X * Revision 3.6 83/05/11 16:06:30 wft
- X * added retry to expandline to resume after failed match which ended in $.
- X * Fixed truncation problem for $19chars followed by@@.
- X *
- X * Revision 3.5 82/12/04 13:20:56 wft
- X * Added expansion of keyword Locker.
- X *
- X * Revision 3.4 82/12/03 12:26:54 wft
- X * Added line number correction in case editing does not start at the
- X * beginning of the file.
- X * Changed keyword expansion to always print a space before closing KDELIM;
- X * Expansion for Header shortened.
- X *
- X * Revision 3.3 82/11/14 14:49:30 wft
- X * removed Suffix from keyword expansion. Replaced fclose with ffclose.
- X * keyreplace() gets log message from delta, not from curlogmsg.
- X * fixed expression overflow in while(c=putc(GETC....
- X * checked nil printing.
- X *
- X * Revision 3.2 82/10/18 21:13:39 wft
- X * I added checks for write errors during the co process, and renamed
- X * expandstring() to xpandstring().
- X *
- X * Revision 3.1 82/10/13 15:52:55 wft
- X * changed type of result of getc() from char to int.
- X * made keyword expansion loop in expandline() portable to machines
- X * without sign-extension.
- X */
- X
- X
- X#include "rcsbase.h"
- X
- libId(editId, "$Id: rcsedit.c,v 5.11 1991/11/03 01:11:44 eggert Exp $")
- X
- static void keyreplace P((enum markers,struct hshentry const*,FILE*));
- X
- X
- XFILE *fcopy; /* result file descriptor */
- char const *resultfile; /* result file name */
- int locker_expansion; /* should the locker name be appended to Id val? */
- X#if !large_memory
- X static RILE *fedit; /* edit file descriptor */
- X static char const *editfile; /* edit pathname */
- X#endif
- static unsigned long editline; /* edit line counter; #lines before cursor */
- static long linecorr; /* #adds - #deletes in each edit run. */
- X /*used to correct editline in case file is not rewound after */
- X /* applying one delta */
- X
- X#define DIRTEMPNAMES 2
- enum maker {notmade, real, effective};
- struct buf dirtfname[DIRTEMPNAMES]; /* unlink these when done */
- static enum maker volatile dirtfmaker[DIRTEMPNAMES]; /* if these are set */
- X
- X
- X#if has_NFS || bad_unlink
- X int
- un_link(s)
- X char const *s;
- X/*
- X * Remove S, even if it is unwritable.
- X * Ignore unlink() ENOENT failures; NFS generates bogus ones.
- X */
- X{
- X# if bad_unlink
- X int e;
- X if (unlink(s) == 0)
- X return 0;
- X e = errno;
- X# if has_NFS
- X if (e == ENOENT)
- X return 0;
- X# endif
- X if (chmod(s, S_IWUSR) != 0) {
- X errno = e;
- X return -1;
- X }
- X# endif
- X# if has_NFS
- X return unlink(s)==0 || errno==ENOENT ? 0 : -1;
- X# else
- X return unlink(s);
- X# endif
- X}
- X#endif
- X
- X#if !has_rename
- X# if !has_NFS
- X# define do_link(s,t) link(s,t)
- X# else
- X static int
- do_link(s, t)
- X char const *s, *t;
- X/* Link S to T, ignoring bogus EEXIST problems due to NFS failures. */
- X{
- X struct stat sb, tb;
- X
- X if (link(s,t) == 0)
- X return 0;
- X if (errno != EEXIST)
- X return -1;
- X if (
- X stat(s, &sb) == 0 &&
- X stat(t, &tb) == 0 &&
- X sb.st_ino == tb.st_ino &&
- X sb.st_dev == tb.st_dev
- X )
- X return 0;
- X errno = EEXIST;
- X return -1;
- X}
- X# endif
- X#endif
- X
- X
- X static exiting void
- editEndsPrematurely()
- X{
- X fatserror("edit script ends prematurely");
- X}
- X
- X static exiting void
- editLineNumberOverflow()
- X{
- X fatserror("edit script refers to line past end of file");
- X}
- X
- X
- X#if large_memory
- X
- X#if has_memmove
- X# define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
- X#else
- X static void
- movelines(s1, s2, n)
- X register Iptr_type *s1;
- X register Iptr_type const *s2;
- X register unsigned long n;
- X{
- X if (s1 < s2)
- X do {
- X *s1++ = *s2++;
- X } while (--n);
- X else {
- X s1 += n;
- X s2 += n;
- X do {
- X *--s1 = *--s2;
- X } while (--n);
- X }
- X}
- X#endif
- X
- X/*
- X * `line' contains pointers to the lines in the currently `edited' file.
- X * It is a 0-origin array that represents linelim-gapsize lines.
- X * line[0..gap-1] and line[gap+gapsize..linelim-1] contain pointers to lines.
- X * line[gap..gap+gapsize-1] contains garbage.
- X *
- X * Any @s in lines are duplicated.
- X * Lines are terminated by \n, or (for a last partial line only) by single @.
- X */
- static Iptr_type *line;
- static unsigned long gap, gapsize, linelim;
- X
- X
- X static void
- insertline(n, l)
- X unsigned long n;
- X Iptr_type l;
- X/* Before line N, insert line L. N is 0-origin. */
- X{
- X if (linelim-gapsize < n)
- X editLineNumberOverflow();
- X if (!gapsize)
- X line =
- X !linelim ?
- X tnalloc(Iptr_type, linelim = gapsize = 1024)
- X : (
- X gap = gapsize = linelim,
- X trealloc(Iptr_type, line, linelim <<= 1)
- X );
- X if (n < gap)
- X movelines(line+n+gapsize, line+n, gap-n);
- X else if (gap < n)
- X movelines(line+gap, line+gap+gapsize, n-gap);
- X
- X line[n] = l;
- X gap = n + 1;
- X gapsize--;
- X}
- X
- X static void
- deletelines(n, nlines)
- X unsigned long n, nlines;
- X/* Delete lines N through N+NLINES-1. N is 0-origin. */
- X{
- X unsigned long l = n + nlines;
- X if (linelim-gapsize < l || l < n)
- X editLineNumberOverflow();
- X if (l < gap)
- X movelines(line+l+gapsize, line+l, gap-l);
- X else if (gap < n)
- X movelines(line+gap, line+gap+gapsize, n-gap);
- X
- X gap = n;
- X gapsize += nlines;
- X}
- X
- X static void
- snapshotline(f, l)
- X register FILE *f;
- X register Iptr_type l;
- X{
- X register int c;
- X do {
- X if ((c = *l++) == SDELIM && *l++ != SDELIM)
- X return;
- X aputc(c, f);
- X } while (c != '\n');
- X}
- X
- X void
- snapshotedit(f)
- X FILE *f;
- X/* Copy the current state of the edits to F. */
- X{
- X register Iptr_type *p, *lim, *l=line;
- X for (p=l, lim=l+gap; p<lim; )
- X snapshotline(f, *p++);
- X for (p+=gapsize, lim=l+linelim; p<lim; )
- X snapshotline(f, *p++);
- X}
- X
- X static void
- finisheditline(fin, fout, l, delta)
- X RILE *fin;
- X FILE *fout;
- X Iptr_type l;
- X struct hshentry const *delta;
- X{
- X Iseek(fin, l);
- X if (expandline(fin, fout, delta, true, (FILE*)0) < 0)
- X faterror("finisheditline internal error");
- X}
- X
- X void
- finishedit(delta, outfile, done)
- X struct hshentry const *delta;
- X FILE *outfile;
- X int done;
- X/*
- X * Doing expansion if DELTA is set, output the state of the edits to OUTFILE.
- X * But do nothing unless DONE is set (which means we are on the last pass).
- X */
- X{
- X if (done) {
- X openfcopy(outfile);
- X outfile = fcopy;
- X if (!delta)
- X snapshotedit(outfile);
- X else {
- X register Iptr_type *p, *lim, *l = line;
- X register RILE *fin = finptr;
- X Iptr_type here = Itell(fin);
- X for (p=l, lim=l+gap; p<lim; )
- X finisheditline(fin, outfile, *p++, delta);
- X for (p+=gapsize, lim=l+linelim; p<lim; )
- X finisheditline(fin, outfile, *p++, delta);
- X Iseek(fin, here);
- X }
- X }
- X}
- X
- X/* Open a temporary FILENAME for output, truncating any previous contents. */
- X# define fopen_update_truncate(filename) fopen(filename, FOPEN_W_WORK)
- X#else /* !large_memory */
- X static FILE *
- fopen_update_truncate(filename)
- X char const *filename;
- X{
- X# if bad_fopen_wplus
- X if (un_link(filename) != 0)
- X efaterror(filename);
- X# endif
- X return fopen(filename, FOPEN_WPLUS_WORK);
- X}
- X#endif
- X
- X
- X void
- openfcopy(f)
- X FILE *f;
- X{
- X if (!(fcopy = f)) {
- X if (!resultfile)
- X resultfile = maketemp(2);
- X if (!(fcopy = fopen_update_truncate(resultfile)))
- X efaterror(resultfile);
- X }
- X}
- X
- X
- X#if !large_memory
- X
- X static void
- swapeditfiles(outfile)
- X FILE *outfile;
- X/* Function: swaps resultfile and editfile, assigns fedit=fcopy,
- X * and rewinds fedit for reading. Set fcopy to outfile if nonnull;
- X * otherwise, set fcopy to be resultfile opened for reading and writing.
- X */
- X{
- X char const *tmpptr;
- X
- X editline = 0; linecorr = 0;
- X if (fseek(fcopy, 0L, SEEK_SET) != 0)
- X Oerror();
- X fedit = fcopy;
- X tmpptr=editfile; editfile=resultfile; resultfile=tmpptr;
- X openfcopy(outfile);
- X}
- X
- X void
- snapshotedit(f)
- X FILE *f;
- X/* Copy the current state of the edits to F. */
- X{
- X finishedit((struct hshentry *)nil, (FILE*)0, false);
- X fastcopy(fedit, f);
- X Irewind(fedit);
- X}
- X
- X void
- finishedit(delta, outfile, done)
- X struct hshentry const *delta;
- X FILE *outfile;
- X int done;
- X/* copy the rest of the edit file and close it (if it exists).
- X * if delta!=nil, perform keyword substitution at the same time.
- X * If DONE is set, we are finishing the last pass.
- X */
- X{
- X register RILE *fe;
- X register FILE *fc;
- X
- X fe = fedit;
- X if (fe) {
- X fc = fcopy;
- X if (delta!=nil) {
- X while (1 < expandline(fe,fc,delta,false,(FILE*)0))
- X ;
- X } else {
- X fastcopy(fe,fc);
- X }
- X Ifclose(fe);
- X }
- X if (!done)
- X swapeditfiles(outfile);
- X}
- X#endif
- X
- X
- X
- X#if large_memory
- X# define copylines(upto,delta) (editline = (upto))
- X#else
- X static void
- copylines(upto,delta)
- X register unsigned long upto;
- X struct hshentry const *delta;
- X/*
- X * Copy input lines editline+1..upto from fedit to fcopy.
- X * If delta != nil, keyword expansion is done simultaneously.
- X * editline is updated. Rewinds a file only if necessary.
- X */
- X{
- X register int c;
- X declarecache;
- X register FILE *fc;
- X register RILE *fe;
- X
- X if (upto < editline) {
- X /* swap files */
- X finishedit((struct hshentry *)nil, (FILE*)0, false);
- X /* assumes edit only during last pass, from the beginning*/
- X }
- X fe = fedit;
- X fc = fcopy;
- X if (editline < upto)
- X if (delta)
- X do {
- X if (expandline(fe,fc,delta,false,(FILE*)0) <= 1)
- X editLineNumberOverflow();
- X } while (++editline < upto);
- X else {
- X setupcache(fe); cache(fe);
- X do {
- X do {
- X cachegeteof(c, editLineNumberOverflow(););
- X aputc(c, fc);
- X } while (c != '\n');
- X } while (++editline < upto);
- X uncache(fe);
- X }
- X}
- X#endif
- X
- X
- X
- X void
- xpandstring(delta)
- X struct hshentry const *delta;
- X/* Function: Reads a string terminated by SDELIM from finptr and writes it
- X * to fcopy. Double SDELIM is replaced with single SDELIM.
- X * Keyword expansion is performed with data from delta.
- X * If foutptr is nonnull, the string is also copied unchanged to foutptr.
- X */
- X{
- X while (1 < expandline(finptr,fcopy,delta,true,foutptr))
- X ;
- X}
- X
- X
- X void
- copystring()
- X/* Function: copies a string terminated with a single SDELIM from finptr to
- X * fcopy, replacing all double SDELIM with a single SDELIM.
- X * If foutptr is nonnull, the string also copied unchanged to foutptr.
- X * editline is incremented by the number of lines copied.
- X * Assumption: next character read is first string character.
- X */
- X{ register c;
- X declarecache;
- X register FILE *frew, *fcop;
- X register int amidline;
- X register RILE *fin;
- X
- X fin = finptr;
- X setupcache(fin); cache(fin);
- X frew = foutptr;
- X fcop = fcopy;
- X amidline = false;
- X for (;;) {
- X GETC(frew,c);
- X switch (c) {
- X case '\n':
- X ++editline;
- X ++rcsline;
- X amidline = false;
- X break;
- X case SDELIM:
- X GETC(frew,c);
- X if (c != SDELIM) {
- X /* end of string */
- X nextc = c;
- X editline += amidline;
- X uncache(fin);
- X return;
- X }
- X /* fall into */
- X default:
- X amidline = true;
- X break;
- X }
- X aputc(c,fcop);
- X }
- X}
- X
- X
- X void
- enterstring()
- X/* Like copystring, except the string is put into the edit data structure. */
- X{
- X#if !large_memory
- X editfile = 0;
- X fedit = 0;
- X editline = linecorr = 0;
- X resultfile = maketemp(1);
- X if (!(fcopy = fopen_update_truncate(resultfile)))
- X efaterror(resultfile);
- X copystring();
- X#else
- X register int c;
- X declarecache;
- X register FILE *frew;
- X register unsigned long e, oe;
- X register int amidline, oamidline;
- X register Iptr_type optr;
- X register RILE *fin;
- X
- X e = 0;
- X gap = 0;
- X gapsize = linelim;
- X fin = finptr;
- X setupcache(fin); cache(fin);
- X advise_access(fin, MADV_NORMAL);
- X frew = foutptr;
- X amidline = false;
- X for (;;) {
- X optr = cachetell();
- X GETC(frew,c);
- X oamidline = amidline;
- X oe = e;
- X switch (c) {
- X case '\n':
- X ++e;
- X ++rcsline;
- X amidline = false;
- X break;
- X case SDELIM:
- X GETC(frew,c);
- X if (c != SDELIM) {
- X /* end of string */
- X nextc = c;
- X editline = e + amidline;
- X linecorr = 0;
- X uncache(fin);
- X return;
- X }
- X /* fall into */
- X default:
- X amidline = true;
- X break;
- X }
- X if (!oamidline)
- X insertline(oe, optr);
- X }
- X#endif
- X}
- X
- X
- X
- X
- X void
- X#if large_memory
- edit_string()
- X#else
- X editstring(delta)
- X struct hshentry const *delta;
- X#endif
- X/*
- X * Read an edit script from finptr and applies it to the edit file.
- X#if !large_memory
- X * The result is written to fcopy.
- X * If delta!=nil, keyword expansion is performed simultaneously.
- X * If running out of lines in fedit, fedit and fcopy are swapped.
- X * editfile is the name of the file that goes with fedit.
- X#endif
- X * If foutptr is set, the edit script is also copied verbatim to foutptr.
- X * Assumes that all these files are open.
- X * resultfile is the name of the file that goes with fcopy.
- X * Assumes the next input character from finptr is the first character of
- X * the edit script. Resets nextc on exit.
- X */
- X{
- X int ed; /* editor command */
- X register int c;
- X declarecache;
- X register FILE *frew;
- X# if !large_memory
- X register FILE *f;
- X unsigned long line_lim = ULONG_MAX;
- X register RILE *fe;
- X# endif
- X register unsigned long i;
- X register RILE *fin;
- X# if large_memory
- X register unsigned long j;
- X# endif
- X struct diffcmd dc;
- X
- X editline += linecorr; linecorr=0; /*correct line number*/
- X frew = foutptr;
- X fin = finptr;
- X setupcache(fin);
- X initdiffcmd(&dc);
- X while (0 <= (ed = getdiffcmd(fin,true,frew,&dc)))
- X#if !large_memory
- X if (line_lim <= dc.line1)
- X editLineNumberOverflow();
- X else
- X#endif
- X if (!ed) {
- X copylines(dc.line1-1, delta);
- X /* skip over unwanted lines */
- X i = dc.nlines;
- X linecorr -= i;
- X editline += i;
- X# if large_memory
- X deletelines(editline+linecorr, i);
- X# else
- X fe = fedit;
- X do {
- X /*skip next line*/
- X do {
- X Igeteof(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } );
- X } while (c != '\n');
- X } while (--i);
- X# endif
- X } else {
- X copylines(dc.line1, delta); /*copy only; no delete*/
- X i = dc.nlines;
- X# if large_memory
- X j = editline+linecorr;
- X# endif
- X linecorr += i;
- X#if !large_memory
- X f = fcopy;
- X if (delta)
- X do {
- X switch (expandline(fin,f,delta,true,frew)) {
- X case 0: case 1:
- X if (i==1)
- X return;
- X /* fall into */
- X case -1:
- X editEndsPrematurely();
- X }
- X } while (--i);
- X else
- X#endif
- X {
- X cache(fin);
- X do {
- X# if large_memory
- X insertline(j++, cachetell());
- X# endif
- X for (;;) {
- X GETC(frew, c);
- X# if !large_memory
- X aputc(c, f);
- X# endif
- X if (c == '\n')
- X break;
- X if (c==SDELIM) {
- X GETC(frew, c);
- X if (c!=SDELIM) {
- X if (--i)
- X editEndsPrematurely();
- X nextc = c;
- X uncache(fin);
- X return;
- X }
- X }
- X }
- X ++rcsline;
- X } while (--i);
- X uncache(fin);
- X }
- X }
- X}
- X
- X
- X
- X/* The rest is for keyword expansion */
- X
- X
- X
- X int
- expandline(infile, outfile, delta, delimstuffed, frewfile)
- X RILE *infile;
- X FILE *outfile, *frewfile;
- X struct hshentry const *delta;
- X int delimstuffed;
- X/*
- X * Read a line from INFILE and write it to OUTFILE.
- X * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
- X * Keyword expansion is performed with data from delta.
- X * If FREWFILE is set, copy the line unchanged to FREWFILE.
- X * DELIMSTUFFED must be true if FREWFILE is set.
- X * Yields -1 if no data is copied, 0 if an incomplete line is copied,
- X * 2 if a complete line is copied; adds 1 to yield if expansion occurred.
- X */
- X{
- X register c;
- X declarecache;
- X register FILE *out, *frew;
- X register char * tp;
- X register int e, ds, r;
- X char const *tlim;
- X static struct buf keyval;
- X enum markers matchresult;
- X
- X setupcache(infile); cache(infile);
- X out = outfile;
- X frew = frewfile;
- X ds = delimstuffed;
- X bufalloc(&keyval, keylength+3);
- X e = 0;
- X r = -1;
- X
- X for (;;) {
- X if (ds) {
- X GETC(frew, c);
- X } else
- X cachegeteof(c, goto uncache_exit;);
- X for (;;) {
- X switch (c) {
- X case SDELIM:
- X if (ds) {
- X GETC(frew, c);
- X if (c != SDELIM) {
- X /* end of string */
- X nextc=c;
- X goto uncache_exit;
- X }
- X }
- X /* fall into */
- X default:
- X aputc(c,out);
- X r = 0;
- X break;
- X
- X case '\n':
- X rcsline += ds;
- X aputc(c,out);
- X r = 2;
- X goto uncache_exit;
- X
- X case KDELIM:
- X r = 0;
- X /* check for keyword */
- X /* first, copy a long enough string into keystring */
- X tp = keyval.string;
- X *tp++ = KDELIM;
- X for (;;) {
- X if (ds) {
- X GETC(frew, c);
- X } else
- X cachegeteof(c, goto keystring_eof;);
- X if (tp < keyval.string+keylength+1)
- X switch (ctab[c]) {
- X case LETTER: case Letter:
- X *tp++ = c;
- X continue;
- X default:
- X break;
- X }
- X break;
- X }
- X *tp++ = c; *tp = '\0';
- X matchresult = trymatch(keyval.string+1);
- X if (matchresult==Nomatch) {
- X tp[-1] = 0;
- X aputs(keyval.string, out);
- X continue; /* last c handled properly */
- X }
- X
- X /* Now we have a keyword terminated with a K/VDELIM */
- X if (c==VDELIM) {
- X /* try to find closing KDELIM, and replace value */
- X tlim = keyval.string + keyval.size;
- X for (;;) {
- X if (ds) {
- X GETC(frew, c);
- X } else
- X cachegeteof(c, goto keystring_eof;);
- X if (c=='\n' || c==KDELIM)
- X break;
- X *tp++ =c;
- X if (tlim <= tp)
- X tp = bufenlarge(&keyval, &tlim);
- X if (c==SDELIM && ds) { /*skip next SDELIM */
- X GETC(frew, c);
- X if (c != SDELIM) {
- X /* end of string before closing KDELIM or newline */
- X nextc = c;
- X goto keystring_eof;
- X }
- X }
- X }
- X if (c!=KDELIM) {
- X /* couldn't find closing KDELIM -- give up */
- X *tp = 0;
- X aputs(keyval.string, out);
- X continue; /* last c handled properly */
- X }
- X }
- X /* now put out the new keyword value */
- X keyreplace(matchresult,delta,out);
- X e = 1;
- X break;
- X }
- X break;
- X }
- X }
- X
- X keystring_eof:
- X *tp = 0;
- X aputs(keyval.string, out);
- X uncache_exit:
- X uncache(infile);
- X return r + e;
- X}
- X
- X
- char const ciklog[ciklogsize] = "checked in with -k by ";
- X
- X static void
- keyreplace(marker,delta,out)
- X enum markers marker;
- X register struct hshentry const *delta;
- X register FILE *out;
- X/* function: outputs the keyword value(s) corresponding to marker.
- X * Attributes are derived from delta.
- X */
- X{
- X register char const *sp, *cp, *date;
- X register char c;
- X register size_t cs, cw, ls;
- X char const *sp1;
- X char datebuf[datesize];
- X int RCSv;
- X
- X sp = Keyword[(int)marker];
- X
- X if (Expand == KEY_EXPAND) {
- X aprintf(out, "%c%s%c", KDELIM, sp, KDELIM);
- X return;
- X }
- X
- X date= delta->date;
- X RCSv = RCSversion;
- X
- X if (Expand == KEYVAL_EXPAND || Expand == KEYVALLOCK_EXPAND)
- X aprintf(out, "%c%s%c%c", KDELIM, sp, VDELIM,
- X marker==Log && RCSv<VERSION(5) ? '\t' : ' '
- X );
- X
- X switch (marker) {
- X case Author:
- X aputs(delta->author, out);
- X break;
- X case Date:
- X aputs(date2str(date,datebuf), out);
- X break;
- X case Id:
- X case Header:
- X aprintf(out, "%s %s %s %s %s",
- X marker==Id || RCSv<VERSION(4)
- X ? basename(RCSfilename)
- X : getfullRCSname(),
- X delta->num,
- X date2str(date, datebuf),
- X delta->author,
- X RCSv==VERSION(3) && delta->lockedby ? "Locked"
- X : delta->state
- X );
- X if (delta->lockedby!=nil)
- X if (VERSION(5) <= RCSv) {
- X if (locker_expansion || Expand==KEYVALLOCK_EXPAND)
- X aprintf(out, " %s", delta->lockedby);
- X } else if (RCSv == VERSION(4))
- X aprintf(out, " Locker: %s", delta->lockedby);
- X break;
- X case Locker:
- X if (delta->lockedby)
- X if (
- X locker_expansion
- X || Expand == KEYVALLOCK_EXPAND
- X || RCSv <= VERSION(4)
- X )
- X aputs(delta->lockedby, out);
- X break;
- X case Log:
- X case RCSfile:
- X aputs(basename(RCSfilename), out);
- X break;
- X case Revision:
- X aputs(delta->num, out);
- X break;
- X case Source:
- X aputs(getfullRCSname(), out);
- X break;
- X case State:
- X aputs(delta->state, out);
- X break;
- X default:
- X break;
- X }
- X if (Expand == KEYVAL_EXPAND || Expand == KEYVALLOCK_EXPAND) {
- X afputc(' ', out);
- X afputc(KDELIM, out);
- X }
- X if (marker == Log) {
- X sp = delta->log.string;
- X ls = delta->log.size;
- X if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
- X return;
- X afputc('\n', out);
- X cp = Comment.string;
- X cw = cs = Comment.size;
- X awrite(cp, cs, out);
- X /* oddity: 2 spaces between date and time, not 1 as usual */
- X sp1 = strchr(date2str(date,datebuf), ' ');
- X aprintf(out, "Revision %s %.*s %s %s",
- X delta->num, (int)(sp1-datebuf), datebuf, sp1, delta->author
- X );
- X /* Do not include state: it may change and is not updated. */
- X /* Comment is the comment leader. */
- X if (VERSION(5) <= RCSv)
- X for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw)
- X ;
- X for (;;) {
- X afputc('\n', out);
- X awrite(cp, cw, out);
- X if (!ls)
- X break;
- X --ls;
- X c = *sp++;
- X if (c != '\n') {
- X awrite(cp+cw, cs-cw, out);
- X do {
- X afputc(c,out);
- X if (!ls)
- X break;
- X --ls;
- X c = *sp++;
- X } while (c != '\n');
- X }
- X }
- X }
- X}
- X
- X#if has_readlink
- X static int
- resolve_symlink(L)
- X struct buf *L;
- X/*
- X * If L is a symbolic link, resolve it to the name that it points to.
- X * If unsuccessful, set errno and yield -1.
- X * If it points to an existing file, yield 1.
- X * Otherwise, set errno=ENOENT and yield 0.
- X */
- X{
- X char *b, a[SIZEABLE_PATH];
- X int e;
- X size_t s;
- X ssize_t r;
- X struct buf bigbuf;
- X unsigned linkcount = MAXSYMLINKS + 1;
- X
- X b = a;
- X s = sizeof(a);
- X bufautobegin(&bigbuf);
- X while ((r = readlink(L->string,b,s)) != -1)
- X if (r == s) {
- X bufalloc(&bigbuf, s<<1);
- X b = bigbuf.string;
- X s = bigbuf.size;
- X } else if (!--linkcount) {
- X errno = ELOOP;
- X return -1;
- X } else {
- X /* Splice symbolic link into L. */
- X b[r] = '\0';
- X L->string[ROOTPATH(b) ? (size_t)0 : dirlen(L->string)] = '\0';
- X bufscat(L, b);
- X }
- X e = errno;
- X bufautoend(&bigbuf);
- X errno = e;
- X switch (e) {
- X case ENXIO:
- X case EINVAL: return 1;
- X case ENOENT: return 0;
- X default: return -1;
- X }
- X}
- X#endif
- X
- X RILE *
- rcswriteopen(RCSbuf, status, mustread)
- X struct buf *RCSbuf;
- X struct stat *status;
- X int mustread;
- X/*
- X * Create the lock file corresponding to RCSNAME.
- X * Then try to open RCSNAME for reading and yield its FILE* descriptor.
- X * Put its status into *STATUS too.
- X * MUSTREAD is true if the file must already exist, too.
- X * If all goes well, discard any previously acquired locks,
- X * and set frewrite to the FILE* descriptor of the lock file,
- X * which will eventually turn into the new RCS file.
- X */
- X{
- X register char *tp;
- X register char const *sp, *RCSname, *x;
- X RILE *f;
- X size_t l;
- X int e, exists, fdesc, previouslock, r;
- X struct buf *dirt;
- X struct stat statbuf;
- X
- X previouslock = frewrite != 0;
- X exists =
- X# if has_readlink
- X resolve_symlink(RCSbuf);
- X# else
- X stat(RCSbuf->string, &statbuf) == 0 ? 1
- X : errno==ENOENT ? 0 : -1;
- X# endif
- X if (exists < (mustread|previouslock))
- X /*
- X * There's an unusual problem with the RCS file;
- X * or the RCS file doesn't exist,
- X * and we must read or we already have a lock elsewhere.
- X */
- X return 0;
- X
- X RCSname = RCSbuf->string;
- X sp = basename(RCSname);
- X l = sp - RCSname;
- X dirt = &dirtfname[previouslock];
- X bufscpy(dirt, RCSname);
- X tp = dirt->string + l;
- X x = rcssuffix(RCSname);
- X# if has_readlink
- X if (!x) {
- X error("symbolic link to non RCS filename `%s'", RCSname);
- X errno = EINVAL;
- X return 0;
- X }
- X# endif
- X if (*sp == *x) {
- X error("RCS filename `%s' incompatible with suffix `%s'", sp, x);
- X errno = EINVAL;
- X return 0;
- X }
- X /* Create a lock file whose name is a function of the RCS filename. */
- X if (*x) {
- X /*
- X * The suffix is nonempty.
- X * The lock filename is the first char of of the suffix,
- X * followed by the RCS filename with last char removed. E.g.:
- X * foo,v RCS filename with suffix ,v
- X * ,foo, lock filename
- X */
- X *tp++ = *x;
- X while (*sp)
- X *tp++ = *sp++;
- X *--tp = 0;
- X } else {
- X /*
- X * The suffix is empty.
- X * The lock filename is the RCS filename
- X * with last char replaced by '_'.
- X */
- X while ((*tp++ = *sp++))
- X ;
- X tp -= 2;
- X if (*tp == '_') {
- X error("RCS filename `%s' ends with `%c'", RCSname, *tp);
- X errno = EINVAL;
- X return 0;
- X }
- X *tp = '_';
- X }
- X
- X sp = tp = dirt->string;
- X
- X f = 0;
- X
- X /*
- X * good news:
- X * open(f, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY) is atomic
- X * according to Posix 1003.1-1990.
- X * bad news:
- X * NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
- X * good news:
- X * (O_TRUNC,READONLY) normally guarantees atomicity even with NFS.
- X * bad news:
- X * If you're root, (O_TRUNC,READONLY) doesn't guarantee atomicity.
- X * good news:
- X * Root-over-the-wire NFS access is rare for security reasons.
- X * This bug has never been reported in practice with RCS.
- X * So we don't worry about this bug.
- X *
- X * An even rarer NFS bug can occur when clients retry requests.
- X * Suppose client A renames the lock file ",f," to "f,v"
- X * at about the same time that client B creates ",f,",
- X * and suppose A's first rename request is delayed, so A reissues it.
- X * The sequence of events might be:
- X * A sends rename(",f,", "f,v")
- X * B sends create(",f,")
- X * A sends retry of rename(",f,", "f,v")
- X * server receives, does, and acknowledges A's first rename()
- X * A receives acknowledgment, and its RCS program exits
- X * server receives, does, and acknowledges B's create()
- X * server receives, does, and acknowledges A's retry of rename()
- X * This not only wrongly deletes B's lock, it removes the RCS file!
- X * Most NFS implementations have idempotency caches that usually prevent
- X * this scenario, but such caches are finite and can be overrun.
- X * This problem afflicts programs that use the traditional
- X * Unix method of using link() and unlink() to get and release locks,
- X * as well as RCS's method of using open() and rename().
- X * There is no easy workaround for either link-unlink or open-rename.
- X * Any new method based on lockf() seemingly would be incompatible with
- X * the old methods; besides, lockf() is notoriously buggy under NFS.
- X * Since this problem afflicts scads of Unix programs, but is so rare
- X * that nobody seems to be worried about it, we won't worry either.
- X */
- X# define READONLY (S_IRUSR|S_IRGRP|S_IROTH)
- X# if !open_can_creat
- X# define create(f) creat(f, READONLY)
- X# else
- X# define create(f) open(f, O_BINARY|O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY)
- X# endif
- X
- X catchints();
- X ignoreints();
- X
- X /*
- X * Create a lock file for an RCS file. This should be atomic, i.e.
- X * if two processes try it simultaneously, at most one should succeed.
- X */
- X seteid();
- X fdesc = create(sp);
- X e = errno;
- X setrid();
- X
- X if (fdesc < 0) {
- X if (e == EACCES && stat(tp,&statbuf) == 0)
- X /* The RCS file is busy. */
- X e = EEXIST;
- X } else {
- X dirtfmaker[0] = effective;
- X e = ENOENT;
- X if (exists) {
- X f = Iopen(RCSname, FOPEN_R, status);
- X e = errno;
- X if (f && previouslock) {
- X /* Discard the previous lock in favor of this one. */
- X Ozclose(&frewrite);
- X seteid();
- X if ((r = un_link(newRCSfilename)) != 0)
- X e = errno;
- X setrid();
- X if (r != 0)
- X enfaterror(e, newRCSfilename);
- X bufscpy(&dirtfname[0], tp);
- X }
- X }
- X if (!(frewrite = fdopen(fdesc, FOPEN_W))) {
- X efaterror(newRCSfilename);
- X }
- X }
- X
- X restoreints();
- X
- X errno = e;
- X return f;
- X}
- X
- X void
- keepdirtemp(name)
- X char const *name;
- X/* Do not unlink name, either because it's not there any more,
- X * or because it has already been unlinked.
- X */
- X{
- X register int i;
- X for (i=DIRTEMPNAMES; 0<=--i; )
- X if (dirtfname[i].string == name) {
- X dirtfmaker[i] = notmade;
- X return;
- X }
- X faterror("keepdirtemp");
- X}
- X
- X char const *
- makedirtemp(name, n)
- X register char const *name;
- X int n;
- X/*
- X * Have maketemp() do all the work if name is null.
- X * Otherwise, create a unique filename in name's dir using n and name
- X * and store it into the dirtfname[n].
- X * Because of storage in tfnames, dirtempunlink() can unlink the file later.
- X * Return a pointer to the filename created.
- X */
- X{
- X register char *tp, *np;
- X register size_t dl;
- X register struct buf *bn;
- X
- X if (!name)
- X return maketemp(n);
- X dl = dirlen(name);
- X bn = &dirtfname[n];
- X bufalloc(bn,
- X# if has_mktemp
- X dl + 9
- X# else
- X strlen(name) + 3
- X# endif
- X );
- X bufscpy(bn, name);
- X np = tp = bn->string;
- X tp += dl;
- X *tp++ = '_';
- X *tp++ = '0'+n;
- X catchints();
- X# if has_mktemp
- X VOID strcpy(tp, "XXXXXX");
- X if (!mktemp(np) || !*np)
- X faterror("can't make temporary file name `%.*s%c_%cXXXXXX'",
- X (int)dl, name, SLASH, '0'+n
- X );
- X# else
- X /*
- X * Posix 1003.1-1990 has no reliable way
- X * to create a unique file in a named directory.
- X * We fudge here. If the working file name is abcde,
- X * the temp filename is _Ncde where N is a digit.
- X */
- X name += dl;
- X if (*name) name++;
- X if (*name) name++;
- X VOID strcpy(tp, name);
- X# endif
- X dirtfmaker[n] = real;
- X return np;
- X}
- X
- X void
- dirtempunlink()
- X/* Clean up makedirtemp() files. May be invoked by signal handler. */
- X{
- X register int i;
- X enum maker m;
- X
- X for (i = DIRTEMPNAMES; 0 <= --i; )
- X if ((m = dirtfmaker[i]) != notmade) {
- X if (m == effective)
- X seteid();
- X VOID un_link(dirtfname[i].string);
- X if (m == effective)
- X setrid();
- X dirtfmaker[i] = notmade;
- X }
- X}
- X
- X
- X int
- X#if has_prototypes
- chnamemod(FILE **fromp, char const *from, char const *to, mode_t mode)
- X /* The `#if has_prototypes' is needed because mode_t might promote to int. */
- X#else
- X chnamemod(fromp,from,to,mode) FILE **fromp; char const *from,*to; mode_t mode;
- X#endif
- X/*
- X * Rename a file (with optional stream pointer *FROMP) from FROM to TO.
- X * FROM already exists.
- X * Change its mode to MODE, before renaming if possible.
- X * If FROMP, close and clear *FROMP before renaming it.
- X * Unlink TO if it already exists.
- X * Return -1 on error (setting errno), 0 otherwise.
- X */
- X{
- X# if bad_a_rename
- X /*
- X * This host is brain damaged. A race condition is possible
- X * while the lock file is temporarily writable.
- X * There doesn't seem to be a workaround.
- X */
- X mode_t mode_while_renaming = mode|S_IWUSR;
- X# else
- X# define mode_while_renaming mode
- X# endif
- X if (fromp) {
- X# if has_fchmod
- X if (fchmod(fileno(*fromp), mode_while_renaming) != 0)
- X return -1;
- X# endif
- X Ozclose(fromp);
- X }
- X# if has_fchmod
- X else
- X# endif
- X if (chmod(from, mode_while_renaming) != 0)
- X return -1;
- X
- X# if !has_rename || bad_b_rename
- X VOID un_link(to);
- X /*
- X * We need not check the result;
- X * link() or rename() will catch it.
- X * No harm is done if TO does not exist.
- X * However, there's a short window of inconsistency
- X * during which TO does not exist.
- X */
- X# endif
- X
- X return
- X# if !has_rename
- X do_link(from,to) != 0 ? -1 : un_link(from)
- X# else
- X rename(from, to) != 0
- X# if has_NFS
- X && errno != ENOENT
- X# endif
- X ? -1
- X# if bad_a_rename
- X : mode != mode_while_renaming ? chmod(to, mode)
- X# endif
- X : 0
- X# endif
- X ;
- X
- X# undef mode_while_renaming
- X}
- X
- X
- X
- X int
- findlock(delete, target)
- X int delete;
- X struct hshentry **target;
- X/*
- X * Find the first lock held by caller and return a pointer
- X * to the locked delta; also removes the lock if DELETE.
- X * If one lock, put it into *TARGET.
- X * Return 0 for no locks, 1 for one, 2 for two or more.
- X */
- X{
- X register struct lock *next, **trail, **found;
- X
- X found = 0;
- X for (trail = &Locks; (next = *trail); trail = &next->nextlock)
- X if (strcmp(getcaller(), next->login) == 0) {
- X if (found) {
- X error("multiple revisions locked by %s; please specify one", getcaller());
- X return 2;
- X }
- X found = trail;
- X }
- X if (!found)
- X return 0;
- X next = *found;
- X *target = next->delta;
- X if (delete) {
- X next->delta->lockedby = nil;
- X *found = next->nextlock;
- X }
- X return 1;
- X}
- X
- X int
- addlock(delta)
- X struct hshentry * delta;
- X/*
- X * Add a lock held by caller to DELTA and yield 1 if successful.
- X * Print an error message and yield -1 if no lock is added because
- X * DELTA is locked by somebody other than caller.
- X * Return 0 if the caller already holds the lock.
- X */
- X{
- X register struct lock *next;
- X
- X next=Locks;
- X for (next = Locks; next; next = next->nextlock)
- X if (cmpnum(delta->num, next->delta->num) == 0)
- X if (strcmp(getcaller(), next->login) == 0)
- X return 0;
- X else {
- X error("revision %s already locked by %s",
- X delta->num, next->login
- X );
- X return -1;
- X }
- X next = ftalloc(struct lock);
- X delta->lockedby = next->login = getcaller();
- X next->delta = delta;
- X next->nextlock = Locks;
- X Locks = next;
- X return 1;
- X}
- X
- X
- X int
- addsymbol(num, name, rebind)
- X char const *num, *name;
- X int rebind;
- X/*
- X * Associate with revision NUM the new symbolic NAME.
- X * If NAME already exists and REBIND is set, associate NAME with NUM;
- X * otherwise, print an error message and return false;
- X * Return true if successful.
- X */
- X{
- X register struct assoc *next;
- X
- X for (next = Symbols; next; next = next->nextassoc)
- X if (strcmp(name, next->symbol) == 0)
- X if (rebind || strcmp(next->num,num) == 0) {
- X next->num = num;
- X return true;
- X } else {
- X error("symbolic name %s already bound to %s",
- X name, next->num
- X );
- X return false;
- X }
- X next = ftalloc(struct assoc);
- X next->symbol = name;
- X next->num = num;
- X next->nextassoc = Symbols;
- X Symbols = next;
- X return true;
- X}
- X
- X
- X
- X char const *
- getcaller()
- X/* Get the caller's login name. */
- X{
- X# if has_setuid
- X return getusername(euid()!=ruid());
- X# else
- X return getusername(false);
- X# endif
- X}
- X
- X
- X int
- checkaccesslist()
- X/*
- X * Return true if caller is the superuser, the owner of the
- X * file, the access list is empty, or caller is on the access list.
- X * Otherwise, print an error message and return false.
- X */
- X{
- X register struct access const *next;
- X
- X if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
- X return true;
- X
- X next = AccessList;
- X do {
- X if (strcmp(getcaller(), next->login) == 0)
- X return true;
- X } while ((next = next->nextaccess));
- X
- X error("user %s not on the access list", getcaller());
- X return false;
- X}
- X
- X
- X int
- dorewrite(lockflag, changed)
- X int lockflag, changed;
- X/*
- X * Do nothing if LOCKFLAG is zero.
- X * Prepare to rewrite an RCS file if CHANGED is positive.
- X * Stop rewriting if CHANGED is zero, because there won't be any changes.
- X * Fail if CHANGED is negative.
- X * Return true on success.
- X */
- X{
- X int r, e;
- X
- X if (lockflag)
- X if (changed) {
- X if (changed < 0)
- X return false;
- X putadmin(frewrite);
- X puttree(Head, frewrite);
- X aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
- X foutptr = frewrite;
- X } else {
- X Ozclose(&frewrite);
- X seteid();
- X ignoreints();
- X r = un_link(newRCSfilename);
- X e = errno;
- X keepdirtemp(newRCSfilename);
- X restoreints();
- X setrid();
- X if (r != 0) {
- X enerror(e, RCSfilename);
- X return false;
- X }
- X }
- X return true;
- X}
- X
- X int
- donerewrite(changed)
- X int changed;
- X/*
- X * Finish rewriting an RCS file if CHANGED is nonzero.
- X * Return true on success.
- X */
- X{
- X int r, e;
- X
- X if (changed && !nerror) {
- X if (finptr) {
- X fastcopy(finptr, frewrite);
- X Izclose(&finptr);
- X }
- X if (1 < RCSstat.st_nlink)
- X warn("breaking hard link to %s", RCSfilename);
- X seteid();
- X ignoreints();
- X r = chnamemod(&frewrite, newRCSfilename, RCSfilename,
- X RCSstat.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH)
- X );
- X e = errno;
- X keepdirtemp(newRCSfilename);
- X restoreints();
- X setrid();
- X if (r != 0) {
- X enerror(e, RCSfilename);
- X error("saved in %s", newRCSfilename);
- X dirtempunlink();
- X return false;
- X }
- X }
- X return true;
- X}
- X
- X void
- aflush(f)
- X FILE *f;
- X{
- X if (fflush(f) != 0)
- X Oerror();
- X}
- END_OF_FILE
- if test 39683 -ne `wc -c <'src/rcsedit.c'`; then
- echo shar: \"'src/rcsedit.c'\" unpacked with wrong size!
- fi
- # end of 'src/rcsedit.c'
- fi
- echo shar: End of archive 8 \(of 11\).
- cp /dev/null ark8isdone
- MISSING=""
- for I in 1 2 3 4 5 6 7 8 9 10 11 ; do
- if test ! -f ark${I}isdone ; then
- MISSING="${MISSING} ${I}"
- fi
- done
- if test "${MISSING}" = "" ; then
- echo You have unpacked all 11 archives.
- rm -f ark[1-9]isdone ark[1-9][0-9]isdone
- else
- echo You still need to unpack the following archives:
- echo " " ${MISSING}
- fi
- ## End of shell archive.
- exit 0
-