head	5.11;
branch	5.11.1;
access;
symbols
	HWGRCSP7F:5.11.1.3
	HWGRCSP6F:5.11.1.3
	HWGRCSP5F:5.11.1.1
	HWGRCSp4:5.11.1.1
	HWGRCSp3:5.11.1
	HWGRCS_Fish:5.11.1
	HWGRCS:5.11.1;
locks; strict;
comment	@ * @;


5.11
date	91.11.03.01.11.44;	author eggert;	state Exp;
branches
	5.11.1.1;
next	;

5.11.1.1
date	93.01.18.14.38.13;	author heinz;	state Exp;
branches;
next	5.11.1.2;

5.11.1.2
date	93.12.04.08.40.36;	author heinz;	state Exp;
branches;
next	5.11.1.3;

5.11.1.3
date	93.12.18.16.12.22;	author heinz;	state Exp;
branches;
next	5.11.1.4;

5.11.1.4
date	94.02.12.19.20.21;	author heinz;	state Exp;
branches;
next	5.11.1.5;

5.11.1.5
date	94.02.14.19.29.45;	author heinz;	state Exp;
branches;
next	5.11.1.6;

5.11.1.6
date	94.03.14.09.19.05;	author heinz;	state Exp;
branches;
next	;


desc
@Checked in with -k 16.jan.93 HWG
@


5.11
log
@checked in with -k by heinz at 1993/01/17 01:53:17
@
text
@/*
 *                     RCS stream editor
 */
/**********************************************************************************
 *                       edits the input file according to a
 *                       script from stdin, generated by diff -n
 *                       performs keyword expansion
 **********************************************************************************
 */

/* Copyright (C) 1982, 1988, 1989 Walter Tichy
   Copyright 1990, 1991 by Paul Eggert
   Distributed under license by the Free Software Foundation, Inc.

This file is part of RCS.

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.

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.

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.

Report problems and direct all questions to:

    rcs-bugs@@cs.purdue.edu

*/


/* $Log: rcsedit.c,v $
 * Revision 5.11  1991/11/03  01:11:44  eggert
 * Move the warning about link breaking to where they're actually being broken.
 *
 * Revision 5.10  1991/10/07  17:32:46  eggert
 * Support piece tables even if !has_mmap.  Fix rare NFS bugs.
 *
 * Revision 5.9  1991/09/17  19:07:40  eggert
 * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
 *
 * Revision 5.8  1991/08/19  03:13:55  eggert
 * Add piece tables, NFS bug workarounds.  Catch odd filenames.  Tune.
 *
 * Revision 5.7  1991/04/21  11:58:21  eggert
 * Fix errno bugs.  Add -x, RCSINIT, MS-DOS support.
 *
 * Revision 5.6  1991/02/25  07:12:40  eggert
 * Fix setuid bug.  Support new link behavior.  Work around broken "w+" fopen.
 *
 * Revision 5.5  1990/12/30  05:07:35  eggert
 * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL).
 *
 * Revision 5.4  1990/11/01  05:03:40  eggert
 * Permit arbitrary data in comment leaders.
 *
 * Revision 5.3  1990/09/11  02:41:13  eggert
 * Tune expandline().
 *
 * Revision 5.2  1990/09/04  08:02:21  eggert
 * Count RCS lines better.  Improve incomplete line handling.
 *
 * Revision 5.1  1990/08/29  07:13:56  eggert
 * Add -kkvl.
 * Fix bug when getting revisions to files ending in incomplete lines.
 * Fix bug in comment leader expansion.
 *
 * Revision 5.0  1990/08/22  08:12:47  eggert
 * Don't require final newline.
 * Don't append "checked in with -k by " to logs,
 * so that checking in a program with -k doesn't change it.
 * Don't generate trailing white space for empty comment leader.
 * Remove compile-time limits; use malloc instead.  Add -k, -V.
 * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
 * Ansify and Posixate.  Check diff's output.
 *
 * Revision 4.8  89/05/01  15:12:35  narten
 * changed copyright header to reflect current distribution rules
 * 
 * Revision 4.7  88/11/08  13:54:14  narten
 * misplaced semicolon caused infinite loop
 * 
 * Revision 4.6  88/08/09  19:12:45  eggert
 * Shrink stdio code size; allow cc -R.
 * 
 * Revision 4.5  87/12/18  11:38:46  narten
 * Changes from the 43. version. Don't know the significance of the
 * first change involving "rewind". Also, additional "lint" cleanup.
 * (Guy Harris)
 * 
 * Revision 4.4  87/10/18  10:32:21  narten
 * Updating version numbers. Changes relative to version 1.1 actually
 * relative to 4.1
 * 
 * Revision 1.4  87/09/24  13:59:29  narten
 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
 * warnings)
 * 
 * Revision 1.3  87/09/15  16:39:39  shepler
 * added an initializatin of the variables editline and linecorr
 * this will be done each time a file is processed.
 * (there was an obscure bug where if co was used to retrieve multiple files
 *  it would dump)
 * fix attributed to  Roy Morris @@FileNet Corp ...!felix!roy
 * 
 * Revision 1.2  87/03/27  14:22:17  jenkins
 * Port to suns
 * 
 * Revision 4.1  83/05/12  13:10:30  wft
 * Added new markers Id and RCSfile; added locker to Header and Id.
 * Overhauled expandline completely() (problem with $01234567890123456789@@).
 * Moved trymatch() and marker table to rcskeys.c.
 * 
 * Revision 3.7  83/05/12  13:04:39  wft
 * Added retry to expandline to resume after failed match which ended in $.
 * Fixed truncation problem for $19chars followed by@@@@.
 * Log no longer expands full path of RCS file.
 * 
 * Revision 3.6  83/05/11  16:06:30  wft
 * added retry to expandline to resume after failed match which ended in $.
 * Fixed truncation problem for $19chars followed by@@@@.
 * 
 * Revision 3.5  82/12/04  13:20:56  wft
 * Added expansion of keyword Locker.
 *
 * Revision 3.4  82/12/03  12:26:54  wft
 * Added line number correction in case editing does not start at the
 * beginning of the file.
 * Changed keyword expansion to always print a space before closing KDELIM;
 * Expansion for Header shortened.
 *
 * Revision 3.3  82/11/14  14:49:30  wft
 * removed Suffix from keyword expansion. Replaced fclose with ffclose.
 * keyreplace() gets log message from delta, not from curlogmsg.
 * fixed expression overflow in while(c=putc(GETC....
 * checked nil printing.
 *
 * Revision 3.2  82/10/18  21:13:39  wft
 * I added checks for write errors during the co process, and renamed
 * expandstring() to xpandstring().
 *
 * Revision 3.1  82/10/13  15:52:55  wft
 * changed type of result of getc() from char to int.
 * made keyword expansion loop in expandline() portable to machines
 * without sign-extension.
 */


#include "rcsbase.h"

libId(editId, "$Id: rcsedit.c,v 5.11 1991/11/03 01:11:44 eggert Exp $")

static void keyreplace P((enum markers,struct hshentry const*,FILE*));


FILE *fcopy;		 /* result file descriptor			    */
char const *resultfile;  /* result file name				    */
int locker_expansion;	 /* should the locker name be appended to Id val?   */
#if !large_memory
	static RILE *fedit; /* edit file descriptor */
	static char const *editfile; /* edit pathname */
#endif
static unsigned long editline; /* edit line counter; #lines before cursor   */
static long linecorr; /* #adds - #deletes in each edit run.		    */
               /*used to correct editline in case file is not rewound after */
               /* applying one delta                                        */

#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 */


#if has_NFS || bad_unlink
	int
un_link(s)
	char const *s;
/*
 * Remove S, even if it is unwritable.
 * Ignore unlink() ENOENT failures; NFS generates bogus ones.
 */
{
#	if bad_unlink
		int e;
		if (unlink(s) == 0)
			return 0;
		e = errno;
#		if has_NFS
			if (e == ENOENT)
				return 0;
#		endif
		if (chmod(s, S_IWUSR) != 0) {
			errno = e;
			return -1;
		}
#	endif
#	if has_NFS
		return unlink(s)==0 || errno==ENOENT  ?  0  :  -1;
#	else
		return unlink(s);
#	endif
}
#endif

#if !has_rename
#  if !has_NFS
#	define do_link(s,t) link(s,t)
#  else
	static int
do_link(s, t)
	char const *s, *t;
/* Link S to T, ignoring bogus EEXIST problems due to NFS failures.  */
{
	struct stat sb, tb;

	if (link(s,t) == 0)
		return 0;
	if (errno != EEXIST)
		return -1;
	if (
	    stat(s, &sb) == 0  &&
	    stat(t, &tb) == 0  &&
	    sb.st_ino == tb.st_ino  &&
	    sb.st_dev == tb.st_dev
	)
		return 0;
	errno = EEXIST;
	return -1;
}
#  endif
#endif


	static exiting void
editEndsPrematurely()
{
	fatserror("edit script ends prematurely");
}

	static exiting void
editLineNumberOverflow()
{
	fatserror("edit script refers to line past end of file");
}


#if large_memory

#if has_memmove
#	define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
#else
	static void
movelines(s1, s2, n)
	register Iptr_type *s1;
	register Iptr_type const *s2;
	register unsigned long n;
{
	if (s1 < s2)
		do {
			*s1++ = *s2++;
		} while (--n);
	else {
		s1 += n;
		s2 += n;
		do {
			*--s1 = *--s2;
		} while (--n);
	}
}
#endif

/*
 * `line' contains pointers to the lines in the currently `edited' file.
 * It is a 0-origin array that represents linelim-gapsize lines.
 * line[0..gap-1] and line[gap+gapsize..linelim-1] contain pointers to lines.
 * line[gap..gap+gapsize-1] contains garbage.
 *
 * Any @@s in lines are duplicated.
 * Lines are terminated by \n, or (for a last partial line only) by single @@.
 */
static Iptr_type *line;
static unsigned long gap, gapsize, linelim;


	static void
insertline(n, l)
	unsigned long n;
	Iptr_type l;
/* Before line N, insert line L.  N is 0-origin.  */
{
	if (linelim-gapsize < n)
	    editLineNumberOverflow();
	if (!gapsize)
	    line =
		!linelim ?
			tnalloc(Iptr_type, linelim = gapsize = 1024)
		: (
			gap = gapsize = linelim,
			trealloc(Iptr_type, line, linelim <<= 1)
		);
	if (n < gap)
	    movelines(line+n+gapsize, line+n, gap-n);
	else if (gap < n)
	    movelines(line+gap, line+gap+gapsize, n-gap);

	line[n] = l;
	gap = n + 1;
	gapsize--;
}

	static void
deletelines(n, nlines)
	unsigned long n, nlines;
/* Delete lines N through N+NLINES-1.  N is 0-origin.  */
{
	unsigned long l = n + nlines;
	if (linelim-gapsize < l  ||  l < n)
	    editLineNumberOverflow();
	if (l < gap)
	    movelines(line+l+gapsize, line+l, gap-l);
	else if (gap < n)
	    movelines(line+gap, line+gap+gapsize, n-gap);

	gap = n;
	gapsize += nlines;
}

	static void
snapshotline(f, l)
	register FILE *f;
	register Iptr_type l;
{
	register int c;
	do {
		if ((c = *l++) == SDELIM  &&  *l++ != SDELIM)
			return;
		aputc(c, f);
	} while (c != '\n');
}

	void
snapshotedit(f)
	FILE *f;
/* Copy the current state of the edits to F.  */
{
	register Iptr_type *p, *lim, *l=line;
	for (p=l, lim=l+gap;  p<lim;  )
		snapshotline(f, *p++);
	for (p+=gapsize, lim=l+linelim;  p<lim;  )
		snapshotline(f, *p++);
}

	static void
finisheditline(fin, fout, l, delta)
	RILE *fin;
	FILE *fout;
	Iptr_type l;
	struct hshentry const *delta;
{
	Iseek(fin, l);
	if (expandline(fin, fout, delta, true, (FILE*)0)  <  0)
		faterror("finisheditline internal error");
}

	void
finishedit(delta, outfile, done)
	struct hshentry const *delta;
	FILE *outfile;
	int done;
/*
 * Doing expansion if DELTA is set, output the state of the edits to OUTFILE.
 * But do nothing unless DONE is set (which means we are on the last pass).
 */
{
	if (done) {
		openfcopy(outfile);
		outfile = fcopy;
		if (!delta)
			snapshotedit(outfile);
		else {
			register Iptr_type *p, *lim, *l = line;
			register RILE *fin = finptr;
			Iptr_type here = Itell(fin);
			for (p=l, lim=l+gap;  p<lim;  )
				finisheditline(fin, outfile, *p++, delta);
			for (p+=gapsize, lim=l+linelim;  p<lim;  )
				finisheditline(fin, outfile, *p++, delta);
			Iseek(fin, here);
		}
	}
}

/* Open a temporary FILENAME for output, truncating any previous contents.  */
#   define fopen_update_truncate(filename) fopen(filename, FOPEN_W_WORK)
#else /* !large_memory */
    static FILE *
fopen_update_truncate(filename)
    char const *filename;
{
#	if bad_fopen_wplus
		if (un_link(filename) != 0)
			efaterror(filename);
#	endif
	return fopen(filename, FOPEN_WPLUS_WORK);
}
#endif


	void
openfcopy(f)
	FILE *f;
{
	if (!(fcopy = f)) {
		if (!resultfile)
			resultfile = maketemp(2);
		if (!(fcopy = fopen_update_truncate(resultfile)))
			efaterror(resultfile);
	}
}


#if !large_memory

	static void
swapeditfiles(outfile)
	FILE *outfile;
/* Function: swaps resultfile and editfile, assigns fedit=fcopy,
 * and rewinds fedit for reading.  Set fcopy to outfile if nonnull;
 * otherwise, set fcopy to be resultfile opened for reading and writing.
 */
{
	char const *tmpptr;

	editline = 0;  linecorr = 0;
	if (fseek(fcopy, 0L, SEEK_SET) != 0)
		Oerror();
	fedit = fcopy;
        tmpptr=editfile; editfile=resultfile; resultfile=tmpptr;
	openfcopy(outfile);
}

	void
snapshotedit(f)
	FILE *f;
/* Copy the current state of the edits to F.  */
{
	finishedit((struct hshentry *)nil, (FILE*)0, false);
	fastcopy(fedit, f);
	Irewind(fedit);
}

	void
finishedit(delta, outfile, done)
	struct hshentry const *delta;
	FILE *outfile;
	int done;
/* copy the rest of the edit file and close it (if it exists).
 * if delta!=nil, perform keyword substitution at the same time.
 * If DONE is set, we are finishing the last pass.
 */
{
	register RILE *fe;
	register FILE *fc;

	fe = fedit;
	if (fe) {
		fc = fcopy;
                if (delta!=nil) {
			while (1 < expandline(fe,fc,delta,false,(FILE*)0))
				;
                } else {
			fastcopy(fe,fc);
                }
		Ifclose(fe);
        }
	if (!done)
		swapeditfiles(outfile);
}
#endif



#if large_memory
#	define copylines(upto,delta) (editline = (upto))
#else
	static void
copylines(upto,delta)
	register unsigned long upto;
	struct hshentry const *delta;
/*
 * Copy input lines editline+1..upto from fedit to fcopy.
 * If delta != nil, keyword expansion is done simultaneously.
 * editline is updated. Rewinds a file only if necessary.
 */
{
	register int c;
	declarecache;
	register FILE *fc;
	register RILE *fe;

	if (upto < editline) {
                /* swap files */
		finishedit((struct hshentry *)nil, (FILE*)0, false);
                /* assumes edit only during last pass, from the beginning*/
        }
	fe = fedit;
	fc = fcopy;
	if (editline < upto)
	    if (delta)
		do {
			if (expandline(fe,fc,delta,false,(FILE*)0) <= 1)
				editLineNumberOverflow();
		} while (++editline < upto);
	    else {
		setupcache(fe); cache(fe);
		do {
			do {
				cachegeteof(c, editLineNumberOverflow(););
				aputc(c, fc);
			} while (c != '\n');
		} while (++editline < upto);
		uncache(fe);
	    }
}
#endif



	void
xpandstring(delta)
	struct hshentry const *delta;
/* Function: Reads a string terminated by SDELIM from finptr and writes it
 * to fcopy. Double SDELIM is replaced with single SDELIM.
 * Keyword expansion is performed with data from delta.
 * If foutptr is nonnull, the string is also copied unchanged to foutptr.
 */
{
	while (1 < expandline(finptr,fcopy,delta,true,foutptr))
		;
}


	void
copystring()
/* Function: copies a string terminated with a single SDELIM from finptr to
 * fcopy, replacing all double SDELIM with a single SDELIM.
 * If foutptr is nonnull, the string also copied unchanged to foutptr.
 * editline is incremented by the number of lines copied.
 * Assumption: next character read is first string character.
 */
{	register c;
	declarecache;
	register FILE *frew, *fcop;
	register int amidline;
	register RILE *fin;

	fin = finptr;
	setupcache(fin); cache(fin);
	frew = foutptr;
	fcop = fcopy;
	amidline = false;
	for (;;) {
		GETC(frew,c);
		switch (c) {
		    case '\n':
			++editline;
			++rcsline;
			amidline = false;
			break;
		    case SDELIM:
			GETC(frew,c);
			if (c != SDELIM) {
				/* end of string */
				nextc = c;
				editline += amidline;
				uncache(fin);
				return;
			}
			/* fall into */
		    default:
			amidline = true;
			break;
                }
		aputc(c,fcop);
        }
}


	void
enterstring()
/* Like copystring, except the string is put into the edit data structure.  */
{
#if !large_memory
	editfile = 0;
	fedit = 0;
	editline = linecorr = 0;
	resultfile = maketemp(1);
	if (!(fcopy = fopen_update_truncate(resultfile)))
		efaterror(resultfile);
	copystring();
#else
	register int c;
	declarecache;
	register FILE *frew;
	register unsigned long e, oe;
	register int amidline, oamidline;
	register Iptr_type optr;
	register RILE *fin;

	e = 0;
	gap = 0;
	gapsize = linelim;
	fin = finptr;
	setupcache(fin); cache(fin);
	advise_access(fin, MADV_NORMAL);
	frew = foutptr;
	amidline = false;
	for (;;) {
		optr = cachetell();
		GETC(frew,c);
		oamidline = amidline;
		oe = e;
		switch (c) {
		    case '\n':
			++e;
			++rcsline;
			amidline = false;
			break;
		    case SDELIM:
			GETC(frew,c);
			if (c != SDELIM) {
				/* end of string */
				nextc = c;
				editline = e + amidline;
				linecorr = 0;
				uncache(fin);
				return;
			}
			/* fall into */
		    default:
			amidline = true;
			break;
		}
		if (!oamidline)
			insertline(oe, optr);
	}
#endif
}




	void
#if large_memory
edit_string()
#else
  editstring(delta)
	struct hshentry const *delta;
#endif
/*
 * Read an edit script from finptr and applies it to the edit file.
#if !large_memory
 * The result is written to fcopy.
 * If delta!=nil, keyword expansion is performed simultaneously.
 * If running out of lines in fedit, fedit and fcopy are swapped.
 * editfile is the name of the file that goes with fedit.
#endif
 * If foutptr is set, the edit script is also copied verbatim to foutptr.
 * Assumes that all these files are open.
 * resultfile is the name of the file that goes with fcopy.
 * Assumes the next input character from finptr is the first character of
 * the edit script. Resets nextc on exit.
 */
{
        int ed; /* editor command */
        register int c;
	declarecache;
	register FILE *frew;
#	if !large_memory
		register FILE *f;
		unsigned long line_lim = ULONG_MAX;
		register RILE *fe;
#	endif
	register unsigned long i;
	register RILE *fin;
#	if large_memory
		register unsigned long j;
#	endif
	struct diffcmd dc;

        editline += linecorr; linecorr=0; /*correct line number*/
	frew = foutptr;
	fin = finptr;
	setupcache(fin);
	initdiffcmd(&dc);
	while (0  <=  (ed = getdiffcmd(fin,true,frew,&dc)))
#if !large_memory
		if (line_lim <= dc.line1)
			editLineNumberOverflow();
		else
#endif
		if (!ed) {
			copylines(dc.line1-1, delta);
                        /* skip over unwanted lines */
			i = dc.nlines;
			linecorr -= i;
			editline += i;
#			if large_memory
			    deletelines(editline+linecorr, i);
#			else
			    fe = fedit;
			    do {
                                /*skip next line*/
				do {
				    Igeteof(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } );
				} while (c != '\n');
			    } while (--i);
#			endif
		} else {
			copylines(dc.line1, delta); /*copy only; no delete*/
			i = dc.nlines;
#			if large_memory
				j = editline+linecorr;
#			endif
			linecorr += i;
#if !large_memory
			f = fcopy;
			if (delta)
			    do {
				switch (expandline(fin,f,delta,true,frew)) {
				    case 0: case 1:
					if (i==1)
					    return;
					/* fall into */
				    case -1:
					editEndsPrematurely();
				}
			    } while (--i);
			else
#endif
			{
			    cache(fin);
			    do {
#				if large_memory
				    insertline(j++, cachetell());
#				endif
				for (;;) {
				    GETC(frew, c);
#				    if !large_memory
					aputc(c, f);
#				    endif
				    if (c == '\n')
					break;
				    if (c==SDELIM) {
					GETC(frew, c);
					if (c!=SDELIM) {
					    if (--i)
						editEndsPrematurely();
					    nextc = c;
					    uncache(fin);
					    return;
					}
				    }
				}
				++rcsline;
			    } while (--i);
			    uncache(fin);
			}
                }
}



/* The rest is for keyword expansion */



	int
expandline(infile, outfile, delta, delimstuffed, frewfile)
	RILE *infile;
	FILE *outfile, *frewfile;
	struct hshentry const *delta;
	int delimstuffed;
/*
 * Read a line from INFILE and write it to OUTFILE.
 * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
 * Keyword expansion is performed with data from delta.
 * If FREWFILE is set, copy the line unchanged to FREWFILE.
 * DELIMSTUFFED must be true if FREWFILE is set.
 * Yields -1 if no data is copied, 0 if an incomplete line is copied,
 * 2 if a complete line is copied; adds 1 to yield if expansion occurred.
 */
{
	register c;
	declarecache;
	register FILE *out, *frew;
	register char * tp;
	register int e, ds, r;
	char const *tlim;
	static struct buf keyval;
        enum markers matchresult;

	setupcache(infile); cache(infile);
	out = outfile;
	frew = frewfile;
	ds = delimstuffed;
	bufalloc(&keyval, keylength+3);
	e = 0;
	r = -1;

        for (;;) {
	    if (ds) {
		GETC(frew, c);
	    } else
		cachegeteof(c, goto uncache_exit;);
	    for (;;) {
		switch (c) {
		    case SDELIM:
			if (ds) {
			    GETC(frew, c);
			    if (c != SDELIM) {
                                /* end of string */
                                nextc=c;
				goto uncache_exit;
			    }
			}
			/* fall into */
		    default:
			aputc(c,out);
			r = 0;
			break;

		    case '\n':
			rcsline += ds;
			aputc(c,out);
			r = 2;
			goto uncache_exit;

		    case KDELIM:
			r = 0;
                        /* check for keyword */
                        /* first, copy a long enough string into keystring */
			tp = keyval.string;
			*tp++ = KDELIM;
			for (;;) {
			    if (ds) {
				GETC(frew, c);
			    } else
				cachegeteof(c, goto keystring_eof;);
			    if (tp < keyval.string+keylength+1)
				switch (ctab[c]) {
				    case LETTER: case Letter:
					*tp++ = c;
					continue;
				    default:
					break;
				}
			    break;
                        }
			*tp++ = c; *tp = '\0';
			matchresult = trymatch(keyval.string+1);
			if (matchresult==Nomatch) {
				tp[-1] = 0;
				aputs(keyval.string, out);
				continue;   /* last c handled properly */
			}

			/* Now we have a keyword terminated with a K/VDELIM */
			if (c==VDELIM) {
			      /* try to find closing KDELIM, and replace value */
			      tlim = keyval.string + keyval.size;
			      for (;;) {
				      if (ds) {
					GETC(frew, c);
				      } else
					cachegeteof(c, goto keystring_eof;);
				      if (c=='\n' || c==KDELIM)
					break;
				      *tp++ =c;
				      if (tlim <= tp)
					  tp = bufenlarge(&keyval, &tlim);
				      if (c==SDELIM && ds) { /*skip next SDELIM */
						GETC(frew, c);
						if (c != SDELIM) {
							/* end of string before closing KDELIM or newline */
							nextc = c;
							goto keystring_eof;
						}
				      }
			      }
			      if (c!=KDELIM) {
				    /* couldn't find closing KDELIM -- give up */
				    *tp = 0;
				    aputs(keyval.string, out);
				    continue;   /* last c handled properly */
			      }
			}
			/* now put out the new keyword value */
			keyreplace(matchresult,delta,out);
			e = 1;
			break;
                }
		break;
	    }
        }

    keystring_eof:
	*tp = 0;
	aputs(keyval.string, out);
    uncache_exit:
	uncache(infile);
	return r + e;
}


char const ciklog[ciklogsize] = "checked in with -k by ";

	static void
keyreplace(marker,delta,out)
	enum markers marker;
	register struct hshentry const *delta;
	register FILE *out;
/* function: outputs the keyword value(s) corresponding to marker.
 * Attributes are derived from delta.
 */
{
	register char const *sp, *cp, *date;
	register char c;
	register size_t cs, cw, ls;
	char const *sp1;
	char datebuf[datesize];
	int RCSv;

	sp = Keyword[(int)marker];

	if (Expand == KEY_EXPAND) {
		aprintf(out, "%c%s%c", KDELIM, sp, KDELIM);
		return;
	}

        date= delta->date;
	RCSv = RCSversion;

	if (Expand == KEYVAL_EXPAND  ||  Expand == KEYVALLOCK_EXPAND)
		aprintf(out, "%c%s%c%c", KDELIM, sp, VDELIM,
			marker==Log && RCSv<VERSION(5)  ?  '\t'  :  ' '
		);

        switch (marker) {
        case Author:
		aputs(delta->author, out);
                break;
        case Date:
		aputs(date2str(date,datebuf), out);
                break;
        case Id:
	case Header:
		aprintf(out, "%s %s %s %s %s",
			  marker==Id || RCSv<VERSION(4)
			? basename(RCSfilename)
			: getfullRCSname(),
			delta->num,
			date2str(date, datebuf),
			delta->author,
			  RCSv==VERSION(3) && delta->lockedby ? "Locked"
			: delta->state
		);
		if (delta->lockedby!=nil)
		    if (VERSION(5) <= RCSv) {
			if (locker_expansion || Expand==KEYVALLOCK_EXPAND)
			    aprintf(out, " %s", delta->lockedby);
		    } else if (RCSv == VERSION(4))
			aprintf(out, " Locker: %s", delta->lockedby);
                break;
        case Locker:
		if (delta->lockedby)
		    if (
				locker_expansion
			||	Expand == KEYVALLOCK_EXPAND
			||	RCSv <= VERSION(4)
		    )
			aputs(delta->lockedby, out);
                break;
        case Log:
        case RCSfile:
		aputs(basename(RCSfilename), out);
                break;
        case Revision:
		aputs(delta->num, out);
                break;
        case Source:
		aputs(getfullRCSname(), out);
                break;
        case State:
		aputs(delta->state, out);
                break;
	default:
		break;
        }
	if (Expand == KEYVAL_EXPAND  ||  Expand == KEYVALLOCK_EXPAND) {
		afputc(' ', out);
		afputc(KDELIM, out);
	}
	if (marker == Log) {
		sp = delta->log.string;
		ls = delta->log.size;
		if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
			return;
		afputc('\n', out);
		cp = Comment.string;
		cw = cs = Comment.size;
		awrite(cp, cs, out);
		/* oddity: 2 spaces between date and time, not 1 as usual */
		sp1 = strchr(date2str(date,datebuf), ' ');
		aprintf(out, "Revision %s  %.*s %s  %s",
		    delta->num, (int)(sp1-datebuf), datebuf, sp1, delta->author
		);
		/* Do not include state: it may change and is not updated.  */
		/* Comment is the comment leader.  */
		if (VERSION(5) <= RCSv)
		    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
			;
		for (;;) {
		    afputc('\n', out);
		    awrite(cp, cw, out);
		    if (!ls)
			break;
		    --ls;
		    c = *sp++;
		    if (c != '\n') {
			awrite(cp+cw, cs-cw, out);
			do {
			    afputc(c,out);
			    if (!ls)
				break;
			    --ls;
			    c = *sp++;
			} while (c != '\n');
		    }
		}
	}
}

#if has_readlink
	static int
resolve_symlink(L)
	struct buf *L;
/*
 * If L is a symbolic link, resolve it to the name that it points to.
 * If unsuccessful, set errno and yield -1.
 * If it points to an existing file, yield 1.
 * Otherwise, set errno=ENOENT and yield 0.
 */
{
	char *b, a[SIZEABLE_PATH];
	int e;
	size_t s;
	ssize_t r;
	struct buf bigbuf;
	unsigned linkcount = MAXSYMLINKS + 1;

	b = a;
	s = sizeof(a);
	bufautobegin(&bigbuf);
	while ((r = readlink(L->string,b,s))  !=  -1)
	    if (r == s) {
		bufalloc(&bigbuf, s<<1);
		b = bigbuf.string;
		s = bigbuf.size;
	    } else if (!--linkcount) {
		errno = ELOOP;
		return -1;
	    } else {
		/* Splice symbolic link into L.  */
		b[r] = '\0';
		L->string[ROOTPATH(b) ? (size_t)0 : dirlen(L->string)]  =  '\0';
		bufscat(L, b);
	    }
	e = errno;
	bufautoend(&bigbuf);
	errno = e;
	switch (e) {
	    case ENXIO:
	    case EINVAL: return 1;
	    case ENOENT: return 0;
	    default: return -1;
	}
}
#endif

	RILE *
rcswriteopen(RCSbuf, status, mustread)
	struct buf *RCSbuf;
	struct stat *status;
	int mustread;
/*
 * Create the lock file corresponding to RCSNAME.
 * Then try to open RCSNAME for reading and yield its FILE* descriptor.
 * Put its status into *STATUS too.
 * MUSTREAD is true if the file must already exist, too.
 * If all goes well, discard any previously acquired locks,
 * and set frewrite to the FILE* descriptor of the lock file,
 * which will eventually turn into the new RCS file.
 */
{
	register char *tp;
	register char const *sp, *RCSname, *x;
	RILE *f;
	size_t l;
	int e, exists, fdesc, previouslock, r;
	struct buf *dirt;
	struct stat statbuf;

	previouslock  =  frewrite != 0;
	exists =
#		if has_readlink
			resolve_symlink(RCSbuf);
#		else
			    stat(RCSbuf->string, &statbuf) == 0  ?  1
			:   errno==ENOENT ? 0 : -1;
#		endif
	if (exists < (mustread|previouslock))
		/*
		 * There's an unusual problem with the RCS file;
		 * or the RCS file doesn't exist,
		 * and we must read or we already have a lock elsewhere.
		 */
		return 0;

	RCSname = RCSbuf->string;
	sp = basename(RCSname);
	l = sp - RCSname;
	dirt = &dirtfname[previouslock];
	bufscpy(dirt, RCSname);
	tp = dirt->string + l;
	x = rcssuffix(RCSname);
#	if has_readlink
	    if (!x) {
		error("symbolic link to non RCS filename `%s'", RCSname);
		errno = EINVAL;
		return 0;
	    }
#	endif
	if (*sp == *x) {
		error("RCS filename `%s' incompatible with suffix `%s'", sp, x);
		errno = EINVAL;
		return 0;
	}
	/* Create a lock file whose name is a function of the RCS filename.  */
	if (*x) {
		/*
		 * The suffix is nonempty.
		 * The lock filename is the first char of of the suffix,
		 * followed by the RCS filename with last char removed.  E.g.:
		 *	foo,v	RCS filename with suffix ,v
		 *	,foo,	lock filename
		 */
		*tp++ = *x;
		while (*sp)
			*tp++ = *sp++;
		*--tp = 0;
	} else {
		/*
		 * The suffix is empty.
		 * The lock filename is the RCS filename
		 * with last char replaced by '_'.
		 */
		while ((*tp++ = *sp++))
			;
		tp -= 2;
		if (*tp == '_') {
			error("RCS filename `%s' ends with `%c'", RCSname, *tp);
			errno = EINVAL;
			return 0;
		}
		*tp = '_';
	}

	sp = tp = dirt->string;

	f = 0;

	/*
	* good news:
	*	open(f, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY) is atomic
	*	according to Posix 1003.1-1990.
	* bad news:
	*	NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
	* good news:
	*	(O_TRUNC,READONLY) normally guarantees atomicity even with NFS.
	* bad news:
	*	If you're root, (O_TRUNC,READONLY) doesn't guarantee atomicity.
	* good news:
	*	Root-over-the-wire NFS access is rare for security reasons.
	*	This bug has never been reported in practice with RCS.
	* So we don't worry about this bug.
	*
	* An even rarer NFS bug can occur when clients retry requests.
	* Suppose client A renames the lock file ",f," to "f,v"
	* at about the same time that client B creates ",f,",
	* and suppose A's first rename request is delayed, so A reissues it.
	* The sequence of events might be:
	*	A sends rename(",f,", "f,v")
	*	B sends create(",f,")
	*	A sends retry of rename(",f,", "f,v")
	*	server receives, does, and acknowledges A's first rename()
	*	A receives acknowledgment, and its RCS program exits
	*	server receives, does, and acknowledges B's create()
	*	server receives, does, and acknowledges A's retry of rename()
	* This not only wrongly deletes B's lock, it removes the RCS file!
	* Most NFS implementations have idempotency caches that usually prevent
	* this scenario, but such caches are finite and can be overrun.
	* This problem afflicts programs that use the traditional
	* Unix method of using link() and unlink() to get and release locks,
	* as well as RCS's method of using open() and rename().
	* There is no easy workaround for either link-unlink or open-rename.
	* Any new method based on lockf() seemingly would be incompatible with
	* the old methods; besides, lockf() is notoriously buggy under NFS.
	* Since this problem afflicts scads of Unix programs, but is so rare
	* that nobody seems to be worried about it, we won't worry either.
	*/
#	define READONLY (S_IRUSR|S_IRGRP|S_IROTH)
#	if !open_can_creat
#		define create(f) creat(f, READONLY)
#	else
#		define create(f) open(f, O_BINARY|O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY)
#	endif

	catchints();
	ignoreints();

	/*
	 * Create a lock file for an RCS file.  This should be atomic, i.e.
	 * if two processes try it simultaneously, at most one should succeed.
	 */
	seteid();
	fdesc = create(sp);
	e = errno;
	setrid();

	if (fdesc < 0) {
		if (e == EACCES  &&  stat(tp,&statbuf) == 0)
			/* The RCS file is busy.  */
			e = EEXIST;
	} else {
		dirtfmaker[0] = effective;
		e = ENOENT;
		if (exists) {
		    f = Iopen(RCSname, FOPEN_R, status);
		    e = errno;
		    if (f && previouslock) {
			/* Discard the previous lock in favor of this one.  */
			Ozclose(&frewrite);
			seteid();
			if ((r = un_link(newRCSfilename)) != 0)
			    e = errno;
			setrid();
			if (r != 0)
			    enfaterror(e, newRCSfilename);
			bufscpy(&dirtfname[0], tp);
		    }
		}
		if (!(frewrite = fdopen(fdesc, FOPEN_W))) {
		    efaterror(newRCSfilename);
		}
	}

	restoreints();

	errno = e;
	return f;
}

	void
keepdirtemp(name)
	char const *name;
/* Do not unlink name, either because it's not there any more,
 * or because it has already been unlinked.
 */
{
	register int i;
	for (i=DIRTEMPNAMES; 0<=--i; )
		if (dirtfname[i].string == name) {
			dirtfmaker[i] = notmade;
			return;
		}
	faterror("keepdirtemp");
}

	char const *
makedirtemp(name, n)
	register char const *name;
	int n;
/*
 * Have maketemp() do all the work if name is null.
 * Otherwise, create a unique filename in name's dir using n and name
 * and store it into the dirtfname[n].
 * Because of storage in tfnames, dirtempunlink() can unlink the file later.
 * Return a pointer to the filename created.
 */
{
	register char *tp, *np;
	register size_t dl;
	register struct buf *bn;

	if (!name)
		return maketemp(n);
	dl = dirlen(name);
	bn = &dirtfname[n];
	bufalloc(bn,
#		if has_mktemp
			dl + 9
#		else
			strlen(name) + 3
#		endif
	);
	bufscpy(bn, name);
	np = tp = bn->string;
	tp += dl;
	*tp++ = '_';
	*tp++ = '0'+n;
	catchints();
#	if has_mktemp
		VOID strcpy(tp, "XXXXXX");
		if (!mktemp(np) || !*np)
		    faterror("can't make temporary file name `%.*s%c_%cXXXXXX'",
			(int)dl, name, SLASH, '0'+n
		    );
#	else
		/*
		 * Posix 1003.1-1990 has no reliable way
		 * to create a unique file in a named directory.
		 * We fudge here.  If the working file name is abcde,
		 * the temp filename is _Ncde where N is a digit.
		 */
		name += dl;
		if (*name) name++;
		if (*name) name++;
		VOID strcpy(tp, name);
#	endif
	dirtfmaker[n] = real;
	return np;
}

	void
dirtempunlink()
/* Clean up makedirtemp() files.  May be invoked by signal handler. */
{
	register int i;
	enum maker m;

	for (i = DIRTEMPNAMES;  0 <= --i;  )
	    if ((m = dirtfmaker[i]) != notmade) {
		if (m == effective)
		    seteid();
		VOID un_link(dirtfname[i].string);
		if (m == effective)
		    setrid();
		dirtfmaker[i] = notmade;
	    }
}


	int
#if has_prototypes
chnamemod(FILE **fromp, char const *from, char const *to, mode_t mode)
  /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
#else
  chnamemod(fromp,from,to,mode) FILE **fromp; char const *from,*to; mode_t mode;
#endif
/*
 * Rename a file (with optional stream pointer *FROMP) from FROM to TO.
 * FROM already exists.
 * Change its mode to MODE, before renaming if possible.
 * If FROMP, close and clear *FROMP before renaming it.
 * Unlink TO if it already exists.
 * Return -1 on error (setting errno), 0 otherwise.
 */
{
#	if bad_a_rename
		/*
		 * This host is brain damaged.  A race condition is possible
		 * while the lock file is temporarily writable.
		 * There doesn't seem to be a workaround.
		 */
		mode_t mode_while_renaming = mode|S_IWUSR;
#	else
#		define mode_while_renaming mode
#	endif
	if (fromp) {
#		if has_fchmod
			if (fchmod(fileno(*fromp), mode_while_renaming) != 0)
				return -1;
#		endif
		Ozclose(fromp);
	}
#	if has_fchmod
	    else
#	endif
	    if (chmod(from, mode_while_renaming) != 0)
		return -1;

#	if !has_rename || bad_b_rename
		VOID un_link(to);
		/*
		 * We need not check the result;
		 * link() or rename() will catch it.
		 * No harm is done if TO does not exist.
		 * However, there's a short window of inconsistency
		 * during which TO does not exist.
		 */
#	endif

	return
#	    if !has_rename
		do_link(from,to) != 0  ?  -1  :  un_link(from)
#	    else
		    rename(from, to) != 0
#		    if has_NFS
			&& errno != ENOENT
#		    endif
		?  -1
#		if bad_a_rename
		:  mode != mode_while_renaming  ?  chmod(to, mode)
#		endif
		:  0
#	    endif
	;

#	undef mode_while_renaming
}



	int
findlock(delete, target)
	int delete;
	struct hshentry **target;
/*
 * Find the first lock held by caller and return a pointer
 * to the locked delta; also removes the lock if DELETE.
 * If one lock, put it into *TARGET.
 * Return 0 for no locks, 1 for one, 2 for two or more.
 */
{
	register struct lock *next, **trail, **found;

	found = 0;
	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
		if (strcmp(getcaller(), next->login)  ==  0) {
			if (found) {
				error("multiple revisions locked by %s; please specify one", getcaller());
				return 2;
			}
			found = trail;
		}
	if (!found)
		return 0;
	next = *found;
	*target = next->delta;
	if (delete) {
		next->delta->lockedby = nil;
		*found = next->nextlock;
	}
	return 1;
}

	int
addlock(delta)
	struct hshentry * delta;
/*
 * Add a lock held by caller to DELTA and yield 1 if successful.
 * Print an error message and yield -1 if no lock is added because
 * DELTA is locked by somebody other than caller.
 * Return 0 if the caller already holds the lock.
 */
{
	register struct lock *next;

	next=Locks;
	for (next = Locks;  next;  next = next->nextlock)
		if (cmpnum(delta->num, next->delta->num) == 0)
			if (strcmp(getcaller(), next->login) == 0)
				return 0;
			else {
				error("revision %s already locked by %s",
				      delta->num, next->login
				);
				return -1;
			}
	next = ftalloc(struct lock);
	delta->lockedby = next->login = getcaller();
	next->delta = delta;
	next->nextlock = Locks;
	Locks = next;
	return 1;
}


	int
addsymbol(num, name, rebind)
	char const *num, *name;
	int rebind;
/*
 * Associate with revision NUM the new symbolic NAME.
 * If NAME already exists and REBIND is set, associate NAME with NUM;
 * otherwise, print an error message and return false;
 * Return true if successful.
 */
{
	register struct assoc *next;

	for (next = Symbols;  next;  next = next->nextassoc)
		if (strcmp(name, next->symbol)  ==  0)
			if (rebind  ||  strcmp(next->num,num) == 0) {
				next->num = num;
				return true;
			} else {
				error("symbolic name %s already bound to %s",
					name, next->num
				);
				return false;
			}
	next = ftalloc(struct assoc);
	next->symbol = name;
	next->num = num;
	next->nextassoc = Symbols;
	Symbols = next;
	return true;
}



	char const *
getcaller()
/* Get the caller's login name.  */
{
#	if has_setuid
		return getusername(euid()!=ruid());
#	else
		return getusername(false);
#	endif
}


	int
checkaccesslist()
/*
 * Return true if caller is the superuser, the owner of the
 * file, the access list is empty, or caller is on the access list.
 * Otherwise, print an error message and return false.
 */
{
	register struct access const *next;

	if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
		return true;

	next = AccessList;
	do {
		if (strcmp(getcaller(), next->login)  ==  0)
			return true;
	} while ((next = next->nextaccess));

	error("user %s not on the access list", getcaller());
	return false;
}


	int
dorewrite(lockflag, changed)
	int lockflag, changed;
/*
 * Do nothing if LOCKFLAG is zero.
 * Prepare to rewrite an RCS file if CHANGED is positive.
 * Stop rewriting if CHANGED is zero, because there won't be any changes.
 * Fail if CHANGED is negative.
 * Return true on success.
 */
{
	int r, e;

	if (lockflag)
		if (changed) {
			if (changed < 0)
				return false;
			putadmin(frewrite);
			puttree(Head, frewrite);
			aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
			foutptr = frewrite;
		} else {
			Ozclose(&frewrite);
			seteid();
			ignoreints();
			r = un_link(newRCSfilename);
			e = errno;
			keepdirtemp(newRCSfilename);
			restoreints();
			setrid();
			if (r != 0) {
				enerror(e, RCSfilename);
				return false;
			}
		}
	return true;
}

	int
donerewrite(changed)
	int changed;
/*
 * Finish rewriting an RCS file if CHANGED is nonzero.
 * Return true on success.
 */
{
	int r, e;

	if (changed && !nerror) {
		if (finptr) {
			fastcopy(finptr, frewrite);
			Izclose(&finptr);
		}
		if (1 < RCSstat.st_nlink)
			warn("breaking hard link to %s", RCSfilename);
		seteid();
		ignoreints();
		r = chnamemod(&frewrite, newRCSfilename, RCSfilename,
			RCSstat.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH)
		);
		e = errno;
		keepdirtemp(newRCSfilename);
		restoreints();
		setrid();
		if (r != 0) {
			enerror(e, RCSfilename);
			error("saved in %s", newRCSfilename);
			dirtempunlink();
			return false;
		}
	}
	return true;
}

	void
aflush(f)
	FILE *f;
{
	if (fflush(f) != 0)
		Oerror();
}
@


5.11.1.1
log
@Start of the AMIGA port of RCS 5.6. I call it HWGRCS now ;^)
@
text
@d2 1
a2 1
 *		       RCS stream editor
d5 3
a7 3
 *			 edits the input file according to a
 *			 script from stdin, generated by diff -n
 *			 performs keyword expansion
d39 1
a39 1
 * Revision 5.11  1991/11/03  01:11:44	eggert
d42 1
a42 1
 * Revision 5.10  1991/10/07  17:32:46	eggert
d55 1
a55 1
 * Fix setuid bug.  Support new link behavior.	Work around broken "w+" fopen.
d85 1
a85 1
 *
d88 1
a88 1
 *
d91 1
a91 1
 *
d96 1
a96 1
 *
d100 1
a100 1
 *
d102 1
a102 1
 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
d104 1
a104 1
 *
d111 1
a111 1
 *
d114 1
a114 1
 *
d119 1
a119 1
 *
d124 1
a124 1
 *
d128 1
a128 1
 *
d171 2
a172 2
	       /*used to correct editline in case file is not rewound after */
	       /* applying one delta					    */
d444 1
a444 1
	tmpptr=editfile; editfile=resultfile; resultfile=tmpptr;
d474 1
a474 1
		if (delta!=nil) {
d477 1
a477 1
		} else {
d479 1
a479 1
		}
d481 1
a481 1
	}
d508 1
a508 1
		/* swap files */
d510 2
a511 2
		/* assumes edit only during last pass, from the beginning*/
	}
d589 1
a589 1
		}
d591 1
a591 1
	}
d681 2
a682 2
	int ed; /* editor command */
	register int c;
d697 1
a697 1
	editline += linecorr; linecorr=0; /*correct line number*/
d710 1
a710 1
			/* skip over unwanted lines */
d719 1
a719 1
				/*skip next line*/
d775 1
a775 1
		}
d807 1
a807 1
	enum markers matchresult;
d817 1
a817 1
	for (;;) {
d828 2
a829 2
				/* end of string */
				nextc=c;
d847 2
a848 2
			/* check for keyword */
			/* first, copy a long enough string into keystring */
d865 1
a865 1
			}
d901 1
a901 1
				    continue;	/* last c handled properly */
d908 1
a908 1
		}
d911 1
a911 1
	}
d947 1
a947 1
	date= delta->date;
d955 2
a956 2
	switch (marker) {
	case Author:
d958 2
a959 2
		break;
	case Date:
d961 2
a962 2
		break;
	case Id:
d980 2
a981 2
		break;
	case Locker:
d989 3
a991 3
		break;
	case Log:
	case RCSfile:
d993 2
a994 2
		break;
	case Revision:
d996 2
a997 2
		break;
	case Source:
d999 2
a1000 2
		break;
	case State:
d1002 1
a1002 1
		break;
d1005 1
a1005 1
	}
d1239 1
a1239 1
	 * Create a lock file for an RCS file.	This should be atomic, i.e.
a1247 7
#ifdef AMIGA
/* Workaround for a bug in SAS/C 6.1!! */
		if(!e)
		{
		    e = EACCES;
		} /* if */
#endif
a1361 11
#ifdef AMIGA
		/* Could this be a still open frewrite file? */
		if((i == 0) && (frewrite != NULL))
		{
		    /* This should be Ozclose, but Ozclose does
		       too much error checking for this. This should be
		       just a try to get rid of this file, too! */
		    fclose(frewrite);
		    frewrite = NULL;
		} /* if */
#endif
d1388 1
a1388 1
		 * This host is brain damaged.	A race condition is possible
d1430 1
a1430 1
		:  mode != mode_while_renaming	?  chmod(to, mode)
@


5.11.1.2
log
@Removed the SAS/C create patch, as the problem has been fixed for 6.3.
@
text
@d2 1
a2 1
 *                     RCS stream editor
d5 3
a7 3
 *                       edits the input file according to a
 *                       script from stdin, generated by diff -n
 *                       performs keyword expansion
d39 1
a39 4
 * Revision 5.11.1.1  1993/01/18  14:38:13  heinz
 * Start of the AMIGA port of RCS 5.6. I call it HWGRCS now ;^)
 *
 * Revision 5.11  1991/11/03  01:11:44  eggert
d42 1
a42 1
 * Revision 5.10  1991/10/07  17:32:46  eggert
d55 1
a55 1
 * Fix setuid bug.  Support new link behavior.  Work around broken "w+" fopen.
d157 1
a157 1
libId(editId, "$Id: rcsedit.c,v 5.11.1.1 1993/01/18 14:38:13 heinz Exp heinz $")
d162 3
a164 3
FILE *fcopy;             /* result file descriptor                          */
char const *resultfile;  /* result file name                                */
int locker_expansion;    /* should the locker name be appended to Id val?   */
d166 2
a167 2
        static RILE *fedit; /* edit file descriptor */
        static char const *editfile; /* edit pathname */
d170 3
a172 3
static long linecorr; /* #adds - #deletes in each edit run.                 */
               /*used to correct editline in case file is not rewound after */
               /* applying one delta                                        */
d176 2
a177 2
struct buf dirtfname[DIRTEMPNAMES];             /* unlink these when done */
static enum maker volatile dirtfmaker[DIRTEMPNAMES];    /* if these are set */
d181 1
a181 1
        int
d183 1
a183 1
        char const *s;
d189 19
a207 19
#       if bad_unlink
                int e;
                if (unlink(s) == 0)
                        return 0;
                e = errno;
#               if has_NFS
                        if (e == ENOENT)
                                return 0;
#               endif
                if (chmod(s, S_IWUSR) != 0) {
                        errno = e;
                        return -1;
                }
#       endif
#       if has_NFS
                return unlink(s)==0 || errno==ENOENT  ?  0  :  -1;
#       else
                return unlink(s);
#       endif
d213 1
a213 1
#       define do_link(s,t) link(s,t)
d215 1
a215 1
        static int
d217 1
a217 1
        char const *s, *t;
d220 1
a220 1
        struct stat sb, tb;
d222 13
a234 13
        if (link(s,t) == 0)
                return 0;
        if (errno != EEXIST)
                return -1;
        if (
            stat(s, &sb) == 0  &&
            stat(t, &tb) == 0  &&
            sb.st_ino == tb.st_ino  &&
            sb.st_dev == tb.st_dev
        )
                return 0;
        errno = EEXIST;
        return -1;
d240 1
a240 1
        static exiting void
d243 1
a243 1
        fatserror("edit script ends prematurely");
d246 1
a246 1
        static exiting void
d249 1
a249 1
        fatserror("edit script refers to line past end of file");
d256 1
a256 1
#       define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
d258 1
a258 1
        static void
d260 15
a274 15
        register Iptr_type *s1;
        register Iptr_type const *s2;
        register unsigned long n;
{
        if (s1 < s2)
                do {
                        *s1++ = *s2++;
                } while (--n);
        else {
                s1 += n;
                s2 += n;
                do {
                        *--s1 = *--s2;
                } while (--n);
        }
d291 1
a291 1
        static void
d293 2
a294 2
        unsigned long n;
        Iptr_type l;
d297 18
a314 18
        if (linelim-gapsize < n)
            editLineNumberOverflow();
        if (!gapsize)
            line =
                !linelim ?
                        tnalloc(Iptr_type, linelim = gapsize = 1024)
                : (
                        gap = gapsize = linelim,
                        trealloc(Iptr_type, line, linelim <<= 1)
                );
        if (n < gap)
            movelines(line+n+gapsize, line+n, gap-n);
        else if (gap < n)
            movelines(line+gap, line+gap+gapsize, n-gap);

        line[n] = l;
        gap = n + 1;
        gapsize--;
d317 1
a317 1
        static void
d319 1
a319 1
        unsigned long n, nlines;
d322 7
a328 7
        unsigned long l = n + nlines;
        if (linelim-gapsize < l  ||  l < n)
            editLineNumberOverflow();
        if (l < gap)
            movelines(line+l+gapsize, line+l, gap-l);
        else if (gap < n)
            movelines(line+gap, line+gap+gapsize, n-gap);
d330 2
a331 2
        gap = n;
        gapsize += nlines;
d334 1
a334 1
        static void
d336 2
a337 2
        register FILE *f;
        register Iptr_type l;
d339 6
a344 6
        register int c;
        do {
                if ((c = *l++) == SDELIM  &&  *l++ != SDELIM)
                        return;
                aputc(c, f);
        } while (c != '\n');
d347 1
a347 1
        void
d349 1
a349 1
        FILE *f;
d352 5
a356 5
        register Iptr_type *p, *lim, *l=line;
        for (p=l, lim=l+gap;  p<lim;  )
                snapshotline(f, *p++);
        for (p+=gapsize, lim=l+linelim;  p<lim;  )
                snapshotline(f, *p++);
d359 1
a359 1
        static void
d361 4
a364 4
        RILE *fin;
        FILE *fout;
        Iptr_type l;
        struct hshentry const *delta;
d366 3
a368 3
        Iseek(fin, l);
        if (expandline(fin, fout, delta, true, (FILE*)0)  <  0)
                faterror("finisheditline internal error");
d371 1
a371 1
        void
d373 3
a375 3
        struct hshentry const *delta;
        FILE *outfile;
        int done;
d381 16
a396 16
        if (done) {
                openfcopy(outfile);
                outfile = fcopy;
                if (!delta)
                        snapshotedit(outfile);
                else {
                        register Iptr_type *p, *lim, *l = line;
                        register RILE *fin = finptr;
                        Iptr_type here = Itell(fin);
                        for (p=l, lim=l+gap;  p<lim;  )
                                finisheditline(fin, outfile, *p++, delta);
                        for (p+=gapsize, lim=l+linelim;  p<lim;  )
                                finisheditline(fin, outfile, *p++, delta);
                        Iseek(fin, here);
                }
        }
d406 5
a410 5
#       if bad_fopen_wplus
                if (un_link(filename) != 0)
                        efaterror(filename);
#       endif
        return fopen(filename, FOPEN_WPLUS_WORK);
d415 1
a415 1
        void
d417 1
a417 1
        FILE *f;
d419 6
a424 6
        if (!(fcopy = f)) {
                if (!resultfile)
                        resultfile = maketemp(2);
                if (!(fcopy = fopen_update_truncate(resultfile)))
                        efaterror(resultfile);
        }
d430 1
a430 1
        static void
d432 1
a432 1
        FILE *outfile;
d438 1
a438 1
        char const *tmpptr;
d440 6
a445 6
        editline = 0;  linecorr = 0;
        if (fseek(fcopy, 0L, SEEK_SET) != 0)
                Oerror();
        fedit = fcopy;
        tmpptr=editfile; editfile=resultfile; resultfile=tmpptr;
        openfcopy(outfile);
d448 1
a448 1
        void
d450 1
a450 1
        FILE *f;
d453 3
a455 3
        finishedit((struct hshentry *)nil, (FILE*)0, false);
        fastcopy(fedit, f);
        Irewind(fedit);
d458 1
a458 1
        void
d460 3
a462 3
        struct hshentry const *delta;
        FILE *outfile;
        int done;
d468 2
a469 2
        register RILE *fe;
        register FILE *fc;
d471 13
a483 13
        fe = fedit;
        if (fe) {
                fc = fcopy;
                if (delta!=nil) {
                        while (1 < expandline(fe,fc,delta,false,(FILE*)0))
                                ;
                } else {
                        fastcopy(fe,fc);
                }
                Ifclose(fe);
        }
        if (!done)
                swapeditfiles(outfile);
d490 1
a490 1
#       define copylines(upto,delta) (editline = (upto))
d492 1
a492 1
        static void
d494 2
a495 2
        register unsigned long upto;
        struct hshentry const *delta;
d502 28
a529 28
        register int c;
        declarecache;
        register FILE *fc;
        register RILE *fe;

        if (upto < editline) {
                /* swap files */
                finishedit((struct hshentry *)nil, (FILE*)0, false);
                /* assumes edit only during last pass, from the beginning*/
        }
        fe = fedit;
        fc = fcopy;
        if (editline < upto)
            if (delta)
                do {
                        if (expandline(fe,fc,delta,false,(FILE*)0) <= 1)
                                editLineNumberOverflow();
                } while (++editline < upto);
            else {
                setupcache(fe); cache(fe);
                do {
                        do {
                                cachegeteof(c, editLineNumberOverflow(););
                                aputc(c, fc);
                        } while (c != '\n');
                } while (++editline < upto);
                uncache(fe);
            }
d535 1
a535 1
        void
d537 1
a537 1
        struct hshentry const *delta;
d544 2
a545 2
        while (1 < expandline(finptr,fcopy,delta,true,foutptr))
                ;
d549 1
a549 1
        void
d557 35
a591 35
{       register c;
        declarecache;
        register FILE *frew, *fcop;
        register int amidline;
        register RILE *fin;

        fin = finptr;
        setupcache(fin); cache(fin);
        frew = foutptr;
        fcop = fcopy;
        amidline = false;
        for (;;) {
                GETC(frew,c);
                switch (c) {
                    case '\n':
                        ++editline;
                        ++rcsline;
                        amidline = false;
                        break;
                    case SDELIM:
                        GETC(frew,c);
                        if (c != SDELIM) {
                                /* end of string */
                                nextc = c;
                                editline += amidline;
                                uncache(fin);
                                return;
                        }
                        /* fall into */
                    default:
                        amidline = true;
                        break;
                }
                aputc(c,fcop);
        }
d595 1
a595 1
        void
d600 7
a606 7
        editfile = 0;
        fedit = 0;
        editline = linecorr = 0;
        resultfile = maketemp(1);
        if (!(fcopy = fopen_update_truncate(resultfile)))
                efaterror(resultfile);
        copystring();
d608 45
a652 45
        register int c;
        declarecache;
        register FILE *frew;
        register unsigned long e, oe;
        register int amidline, oamidline;
        register Iptr_type optr;
        register RILE *fin;

        e = 0;
        gap = 0;
        gapsize = linelim;
        fin = finptr;
        setupcache(fin); cache(fin);
        advise_access(fin, MADV_NORMAL);
        frew = foutptr;
        amidline = false;
        for (;;) {
                optr = cachetell();
                GETC(frew,c);
                oamidline = amidline;
                oe = e;
                switch (c) {
                    case '\n':
                        ++e;
                        ++rcsline;
                        amidline = false;
                        break;
                    case SDELIM:
                        GETC(frew,c);
                        if (c != SDELIM) {
                                /* end of string */
                                nextc = c;
                                editline = e + amidline;
                                linecorr = 0;
                                uncache(fin);
                                return;
                        }
                        /* fall into */
                    default:
                        amidline = true;
                        break;
                }
                if (!oamidline)
                        insertline(oe, optr);
        }
d659 1
a659 1
        void
d664 1
a664 1
        struct hshentry const *delta;
d681 22
a702 22
        int ed; /* editor command */
        register int c;
        declarecache;
        register FILE *frew;
#       if !large_memory
                register FILE *f;
                unsigned long line_lim = ULONG_MAX;
                register RILE *fe;
#       endif
        register unsigned long i;
        register RILE *fin;
#       if large_memory
                register unsigned long j;
#       endif
        struct diffcmd dc;

        editline += linecorr; linecorr=0; /*correct line number*/
        frew = foutptr;
        fin = finptr;
        setupcache(fin);
        initdiffcmd(&dc);
        while (0  <=  (ed = getdiffcmd(fin,true,frew,&dc)))
d704 3
a706 3
                if (line_lim <= dc.line1)
                        editLineNumberOverflow();
                else
d708 24
a731 24
                if (!ed) {
                        copylines(dc.line1-1, delta);
                        /* skip over unwanted lines */
                        i = dc.nlines;
                        linecorr -= i;
                        editline += i;
#                       if large_memory
                            deletelines(editline+linecorr, i);
#                       else
                            fe = fedit;
                            do {
                                /*skip next line*/
                                do {
                                    Igeteof(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } );
                                } while (c != '\n');
                            } while (--i);
#                       endif
                } else {
                        copylines(dc.line1, delta); /*copy only; no delete*/
                        i = dc.nlines;
#                       if large_memory
                                j = editline+linecorr;
#                       endif
                        linecorr += i;
d733 13
a745 13
                        f = fcopy;
                        if (delta)
                            do {
                                switch (expandline(fin,f,delta,true,frew)) {
                                    case 0: case 1:
                                        if (i==1)
                                            return;
                                        /* fall into */
                                    case -1:
                                        editEndsPrematurely();
                                }
                            } while (--i);
                        else
d747 29
a775 29
                        {
                            cache(fin);
                            do {
#                               if large_memory
                                    insertline(j++, cachetell());
#                               endif
                                for (;;) {
                                    GETC(frew, c);
#                                   if !large_memory
                                        aputc(c, f);
#                                   endif
                                    if (c == '\n')
                                        break;
                                    if (c==SDELIM) {
                                        GETC(frew, c);
                                        if (c!=SDELIM) {
                                            if (--i)
                                                editEndsPrematurely();
                                            nextc = c;
                                            uncache(fin);
                                            return;
                                        }
                                    }
                                }
                                ++rcsline;
                            } while (--i);
                            uncache(fin);
                        }
                }
d784 1
a784 1
        int
d786 4
a789 4
        RILE *infile;
        FILE *outfile, *frewfile;
        struct hshentry const *delta;
        int delimstuffed;
d800 112
a911 112
        register c;
        declarecache;
        register FILE *out, *frew;
        register char * tp;
        register int e, ds, r;
        char const *tlim;
        static struct buf keyval;
        enum markers matchresult;

        setupcache(infile); cache(infile);
        out = outfile;
        frew = frewfile;
        ds = delimstuffed;
        bufalloc(&keyval, keylength+3);
        e = 0;
        r = -1;

        for (;;) {
            if (ds) {
                GETC(frew, c);
            } else
                cachegeteof(c, goto uncache_exit;);
            for (;;) {
                switch (c) {
                    case SDELIM:
                        if (ds) {
                            GETC(frew, c);
                            if (c != SDELIM) {
                                /* end of string */
                                nextc=c;
                                goto uncache_exit;
                            }
                        }
                        /* fall into */
                    default:
                        aputc(c,out);
                        r = 0;
                        break;

                    case '\n':
                        rcsline += ds;
                        aputc(c,out);
                        r = 2;
                        goto uncache_exit;

                    case KDELIM:
                        r = 0;
                        /* check for keyword */
                        /* first, copy a long enough string into keystring */
                        tp = keyval.string;
                        *tp++ = KDELIM;
                        for (;;) {
                            if (ds) {
                                GETC(frew, c);
                            } else
                                cachegeteof(c, goto keystring_eof;);
                            if (tp < keyval.string+keylength+1)
                                switch (ctab[c]) {
                                    case LETTER: case Letter:
                                        *tp++ = c;
                                        continue;
                                    default:
                                        break;
                                }
                            break;
                        }
                        *tp++ = c; *tp = '\0';
                        matchresult = trymatch(keyval.string+1);
                        if (matchresult==Nomatch) {
                                tp[-1] = 0;
                                aputs(keyval.string, out);
                                continue;   /* last c handled properly */
                        }

                        /* Now we have a keyword terminated with a K/VDELIM */
                        if (c==VDELIM) {
                              /* try to find closing KDELIM, and replace value */
                              tlim = keyval.string + keyval.size;
                              for (;;) {
                                      if (ds) {
                                        GETC(frew, c);
                                      } else
                                        cachegeteof(c, goto keystring_eof;);
                                      if (c=='\n' || c==KDELIM)
                                        break;
                                      *tp++ =c;
                                      if (tlim <= tp)
                                          tp = bufenlarge(&keyval, &tlim);
                                      if (c==SDELIM && ds) { /*skip next SDELIM */
                                                GETC(frew, c);
                                                if (c != SDELIM) {
                                                        /* end of string before closing KDELIM or newline */
                                                        nextc = c;
                                                        goto keystring_eof;
                                                }
                                      }
                              }
                              if (c!=KDELIM) {
                                    /* couldn't find closing KDELIM -- give up */
                                    *tp = 0;
                                    aputs(keyval.string, out);
                                    continue;   /* last c handled properly */
                              }
                        }
                        /* now put out the new keyword value */
                        keyreplace(matchresult,delta,out);
                        e = 1;
                        break;
                }
                break;
            }
        }
d914 2
a915 2
        *tp = 0;
        aputs(keyval.string, out);
d917 2
a918 2
        uncache(infile);
        return r + e;
d924 1
a924 1
        static void
d926 3
a928 3
        enum markers marker;
        register struct hshentry const *delta;
        register FILE *out;
d933 115
a1047 115
        register char const *sp, *cp, *date;
        register char c;
        register size_t cs, cw, ls;
        char const *sp1;
        char datebuf[datesize];
        int RCSv;

        sp = Keyword[(int)marker];

        if (Expand == KEY_EXPAND) {
                aprintf(out, "%c%s%c", KDELIM, sp, KDELIM);
                return;
        }

        date= delta->date;
        RCSv = RCSversion;

        if (Expand == KEYVAL_EXPAND  ||  Expand == KEYVALLOCK_EXPAND)
                aprintf(out, "%c%s%c%c", KDELIM, sp, VDELIM,
                        marker==Log && RCSv<VERSION(5)  ?  '\t'  :  ' '
                );

        switch (marker) {
        case Author:
                aputs(delta->author, out);
                break;
        case Date:
                aputs(date2str(date,datebuf), out);
                break;
        case Id:
        case Header:
                aprintf(out, "%s %s %s %s %s",
                          marker==Id || RCSv<VERSION(4)
                        ? basename(RCSfilename)
                        : getfullRCSname(),
                        delta->num,
                        date2str(date, datebuf),
                        delta->author,
                          RCSv==VERSION(3) && delta->lockedby ? "Locked"
                        : delta->state
                );
                if (delta->lockedby!=nil)
                    if (VERSION(5) <= RCSv) {
                        if (locker_expansion || Expand==KEYVALLOCK_EXPAND)
                            aprintf(out, " %s", delta->lockedby);
                    } else if (RCSv == VERSION(4))
                        aprintf(out, " Locker: %s", delta->lockedby);
                break;
        case Locker:
                if (delta->lockedby)
                    if (
                                locker_expansion
                        ||      Expand == KEYVALLOCK_EXPAND
                        ||      RCSv <= VERSION(4)
                    )
                        aputs(delta->lockedby, out);
                break;
        case Log:
        case RCSfile:
                aputs(basename(RCSfilename), out);
                break;
        case Revision:
                aputs(delta->num, out);
                break;
        case Source:
                aputs(getfullRCSname(), out);
                break;
        case State:
                aputs(delta->state, out);
                break;
        default:
                break;
        }
        if (Expand == KEYVAL_EXPAND  ||  Expand == KEYVALLOCK_EXPAND) {
                afputc(' ', out);
                afputc(KDELIM, out);
        }
        if (marker == Log) {
                sp = delta->log.string;
                ls = delta->log.size;
                if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
                        return;
                afputc('\n', out);
                cp = Comment.string;
                cw = cs = Comment.size;
                awrite(cp, cs, out);
                /* oddity: 2 spaces between date and time, not 1 as usual */
                sp1 = strchr(date2str(date,datebuf), ' ');
                aprintf(out, "Revision %s  %.*s %s  %s",
                    delta->num, (int)(sp1-datebuf), datebuf, sp1, delta->author
                );
                /* Do not include state: it may change and is not updated.  */
                /* Comment is the comment leader.  */
                if (VERSION(5) <= RCSv)
                    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
                        ;
                for (;;) {
                    afputc('\n', out);
                    awrite(cp, cw, out);
                    if (!ls)
                        break;
                    --ls;
                    c = *sp++;
                    if (c != '\n') {
                        awrite(cp+cw, cs-cw, out);
                        do {
                            afputc(c,out);
                            if (!ls)
                                break;
                            --ls;
                            c = *sp++;
                        } while (c != '\n');
                    }
                }
        }
d1051 1
a1051 1
        static int
d1053 1
a1053 1
        struct buf *L;
d1061 33
a1093 33
        char *b, a[SIZEABLE_PATH];
        int e;
        size_t s;
        ssize_t r;
        struct buf bigbuf;
        unsigned linkcount = MAXSYMLINKS + 1;

        b = a;
        s = sizeof(a);
        bufautobegin(&bigbuf);
        while ((r = readlink(L->string,b,s))  !=  -1)
            if (r == s) {
                bufalloc(&bigbuf, s<<1);
                b = bigbuf.string;
                s = bigbuf.size;
            } else if (!--linkcount) {
                errno = ELOOP;
                return -1;
            } else {
                /* Splice symbolic link into L.  */
                b[r] = '\0';
                L->string[ROOTPATH(b) ? (size_t)0 : dirlen(L->string)]  =  '\0';
                bufscat(L, b);
            }
        e = errno;
        bufautoend(&bigbuf);
        errno = e;
        switch (e) {
            case ENXIO:
            case EINVAL: return 1;
            case ENOENT: return 0;
            default: return -1;
        }
d1097 1
a1097 1
        RILE *
d1099 3
a1101 3
        struct buf *RCSbuf;
        struct stat *status;
        int mustread;
d1112 168
a1279 161
        register char *tp;
        register char const *sp, *RCSname, *x;
        RILE *f;
        size_t l;
        int e, exists, fdesc, previouslock, r;
        struct buf *dirt;
        struct stat statbuf;

        previouslock  =  frewrite != 0;
        exists =
#               if has_readlink
                        resolve_symlink(RCSbuf);
#               else
                            stat(RCSbuf->string, &statbuf) == 0  ?  1
                        :   errno==ENOENT ? 0 : -1;
#               endif
        if (exists < (mustread|previouslock))
                /*
                 * There's an unusual problem with the RCS file;
                 * or the RCS file doesn't exist,
                 * and we must read or we already have a lock elsewhere.
                 */
                return 0;

        RCSname = RCSbuf->string;
        sp = basename(RCSname);
        l = sp - RCSname;
        dirt = &dirtfname[previouslock];
        bufscpy(dirt, RCSname);
        tp = dirt->string + l;
        x = rcssuffix(RCSname);
#       if has_readlink
            if (!x) {
                error("symbolic link to non RCS filename `%s'", RCSname);
                errno = EINVAL;
                return 0;
            }
#       endif
        if (*sp == *x) {
                error("RCS filename `%s' incompatible with suffix `%s'", sp, x);
                errno = EINVAL;
                return 0;
        }
        /* Create a lock file whose name is a function of the RCS filename.  */
        if (*x) {
                /*
                 * The suffix is nonempty.
                 * The lock filename is the first char of of the suffix,
                 * followed by the RCS filename with last char removed.  E.g.:
                 *      foo,v   RCS filename with suffix ,v
                 *      ,foo,   lock filename
                 */
                *tp++ = *x;
                while (*sp)
                        *tp++ = *sp++;
                *--tp = 0;
        } else {
                /*
                 * The suffix is empty.
                 * The lock filename is the RCS filename
                 * with last char replaced by '_'.
                 */
                while ((*tp++ = *sp++))
                        ;
                tp -= 2;
                if (*tp == '_') {
                        error("RCS filename `%s' ends with `%c'", RCSname, *tp);
                        errno = EINVAL;
                        return 0;
                }
                *tp = '_';
        }

        sp = tp = dirt->string;

        f = 0;

        /*
        * good news:
        *       open(f, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY) is atomic
        *       according to Posix 1003.1-1990.
        * bad news:
        *       NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
        * good news:
        *       (O_TRUNC,READONLY) normally guarantees atomicity even with NFS.
        * bad news:
        *       If you're root, (O_TRUNC,READONLY) doesn't guarantee atomicity.
        * good news:
        *       Root-over-the-wire NFS access is rare for security reasons.
        *       This bug has never been reported in practice with RCS.
        * So we don't worry about this bug.
        *
        * An even rarer NFS bug can occur when clients retry requests.
        * Suppose client A renames the lock file ",f," to "f,v"
        * at about the same time that client B creates ",f,",
        * and suppose A's first rename request is delayed, so A reissues it.
        * The sequence of events might be:
        *       A sends rename(",f,", "f,v")
        *       B sends create(",f,")
        *       A sends retry of rename(",f,", "f,v")
        *       server receives, does, and acknowledges A's first rename()
        *       A receives acknowledgment, and its RCS program exits
        *       server receives, does, and acknowledges B's create()
        *       server receives, does, and acknowledges A's retry of rename()
        * This not only wrongly deletes B's lock, it removes the RCS file!
        * Most NFS implementations have idempotency caches that usually prevent
        * this scenario, but such caches are finite and can be overrun.
        * This problem afflicts programs that use the traditional
        * Unix method of using link() and unlink() to get and release locks,
        * as well as RCS's method of using open() and rename().
        * There is no easy workaround for either link-unlink or open-rename.
        * Any new method based on lockf() seemingly would be incompatible with
        * the old methods; besides, lockf() is notoriously buggy under NFS.
        * Since this problem afflicts scads of Unix programs, but is so rare
        * that nobody seems to be worried about it, we won't worry either.
        */
#       define READONLY (S_IRUSR|S_IRGRP|S_IROTH)
#       if !open_can_creat
#               define create(f) creat(f, READONLY)
#       else
#               define create(f) open(f, O_BINARY|O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY)
#       endif

        catchints();
        ignoreints();

        /*
         * Create a lock file for an RCS file.  This should be atomic, i.e.
         * if two processes try it simultaneously, at most one should succeed.
         */
        seteid();
        fdesc = create(sp);
        e = errno;
        setrid();

        if (fdesc < 0) {
                if (e == EACCES  &&  stat(tp,&statbuf) == 0)
                        /* The RCS file is busy.  */
                        e = EEXIST;
        } else {
                dirtfmaker[0] = effective;
                e = ENOENT;
                if (exists) {
                    f = Iopen(RCSname, FOPEN_R, status);
                    e = errno;
                    if (f && previouslock) {
                        /* Discard the previous lock in favor of this one.  */
                        Ozclose(&frewrite);
                        seteid();
                        if ((r = un_link(newRCSfilename)) != 0)
                            e = errno;
                        setrid();
                        if (r != 0)
                            enfaterror(e, newRCSfilename);
                        bufscpy(&dirtfname[0], tp);
                    }
                }
                if (!(frewrite = fdopen(fdesc, FOPEN_W))) {
                    efaterror(newRCSfilename);
                }
        }
d1281 1
a1281 1
        restoreints();
d1283 2
a1284 2
        errno = e;
        return f;
d1287 1
a1287 1
        void
d1289 1
a1289 1
        char const *name;
d1294 7
a1300 7
        register int i;
        for (i=DIRTEMPNAMES; 0<=--i; )
                if (dirtfname[i].string == name) {
                        dirtfmaker[i] = notmade;
                        return;
                }
        faterror("keepdirtemp");
d1303 1
a1303 1
        char const *
d1305 2
a1306 2
        register char const *name;
        int n;
d1315 41
a1355 41
        register char *tp, *np;
        register size_t dl;
        register struct buf *bn;

        if (!name)
                return maketemp(n);
        dl = dirlen(name);
        bn = &dirtfname[n];
        bufalloc(bn,
#               if has_mktemp
                        dl + 9
#               else
                        strlen(name) + 3
#               endif
        );
        bufscpy(bn, name);
        np = tp = bn->string;
        tp += dl;
        *tp++ = '_';
        *tp++ = '0'+n;
        catchints();
#       if has_mktemp
                VOID strcpy(tp, "XXXXXX");
                if (!mktemp(np) || !*np)
                    faterror("can't make temporary file name `%.*s%c_%cXXXXXX'",
                        (int)dl, name, SLASH, '0'+n
                    );
#       else
                /*
                 * Posix 1003.1-1990 has no reliable way
                 * to create a unique file in a named directory.
                 * We fudge here.  If the working file name is abcde,
                 * the temp filename is _Ncde where N is a digit.
                 */
                name += dl;
                if (*name) name++;
                if (*name) name++;
                VOID strcpy(tp, name);
#       endif
        dirtfmaker[n] = real;
        return np;
d1358 1
a1358 1
        void
d1362 2
a1363 2
        register int i;
        enum maker m;
d1365 4
a1368 4
        for (i = DIRTEMPNAMES;  0 <= --i;  )
            if ((m = dirtfmaker[i]) != notmade) {
                if (m == effective)
                    seteid();
d1370 9
a1378 9
                /* Could this be a still open frewrite file? */
                if((i == 0) && (frewrite != NULL))
                {
                    /* This should be Ozclose, but Ozclose does
                       too much error checking for this. This should be
                       just a try to get rid of this file, too! */
                    fclose(frewrite);
                    frewrite = NULL;
                } /* if */
d1380 5
a1384 5
                VOID un_link(dirtfname[i].string);
                if (m == effective)
                    setrid();
                dirtfmaker[i] = notmade;
            }
d1388 1
a1388 1
        int
d1404 49
a1452 49
#       if bad_a_rename
                /*
                 * This host is brain damaged.  A race condition is possible
                 * while the lock file is temporarily writable.
                 * There doesn't seem to be a workaround.
                 */
                mode_t mode_while_renaming = mode|S_IWUSR;
#       else
#               define mode_while_renaming mode
#       endif
        if (fromp) {
#               if has_fchmod
                        if (fchmod(fileno(*fromp), mode_while_renaming) != 0)
                                return -1;
#               endif
                Ozclose(fromp);
        }
#       if has_fchmod
            else
#       endif
            if (chmod(from, mode_while_renaming) != 0)
                return -1;

#       if !has_rename || bad_b_rename
                VOID un_link(to);
                /*
                 * We need not check the result;
                 * link() or rename() will catch it.
                 * No harm is done if TO does not exist.
                 * However, there's a short window of inconsistency
                 * during which TO does not exist.
                 */
#       endif

        return
#           if !has_rename
                do_link(from,to) != 0  ?  -1  :  un_link(from)
#           else
                    rename(from, to) != 0
#                   if has_NFS
                        && errno != ENOENT
#                   endif
                ?  -1
#               if bad_a_rename
                :  mode != mode_while_renaming  ?  chmod(to, mode)
#               endif
                :  0
#           endif
        ;
d1454 1
a1454 1
#       undef mode_while_renaming
d1459 1
a1459 1
        int
d1461 2
a1462 2
        int delete;
        struct hshentry **target;
d1470 1
a1470 1
        register struct lock *next, **trail, **found;
d1472 18
a1489 18
        found = 0;
        for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
                if (strcmp(getcaller(), next->login)  ==  0) {
                        if (found) {
                                error("multiple revisions locked by %s; please specify one", getcaller());
                                return 2;
                        }
                        found = trail;
                }
        if (!found)
                return 0;
        next = *found;
        *target = next->delta;
        if (delete) {
                next->delta->lockedby = nil;
                *found = next->nextlock;
        }
        return 1;
d1492 1
a1492 1
        int
d1494 1
a1494 1
        struct hshentry * delta;
d1502 1
a1502 1
        register struct lock *next;
d1504 17
a1520 17
        next=Locks;
        for (next = Locks;  next;  next = next->nextlock)
                if (cmpnum(delta->num, next->delta->num) == 0)
                        if (strcmp(getcaller(), next->login) == 0)
                                return 0;
                        else {
                                error("revision %s already locked by %s",
                                      delta->num, next->login
                                );
                                return -1;
                        }
        next = ftalloc(struct lock);
        delta->lockedby = next->login = getcaller();
        next->delta = delta;
        next->nextlock = Locks;
        Locks = next;
        return 1;
d1524 1
a1524 1
        int
d1526 2
a1527 2
        char const *num, *name;
        int rebind;
d1535 1
a1535 1
        register struct assoc *next;
d1537 17
a1553 17
        for (next = Symbols;  next;  next = next->nextassoc)
                if (strcmp(name, next->symbol)  ==  0)
                        if (rebind  ||  strcmp(next->num,num) == 0) {
                                next->num = num;
                                return true;
                        } else {
                                error("symbolic name %s already bound to %s",
                                        name, next->num
                                );
                                return false;
                        }
        next = ftalloc(struct assoc);
        next->symbol = name;
        next->num = num;
        next->nextassoc = Symbols;
        Symbols = next;
        return true;
d1558 1
a1558 1
        char const *
d1562 5
a1566 5
#       if has_setuid
                return getusername(euid()!=ruid());
#       else
                return getusername(false);
#       endif
d1570 1
a1570 1
        int
d1578 1
a1578 1
        register struct access const *next;
d1580 2
a1581 2
        if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
                return true;
d1583 5
a1587 5
        next = AccessList;
        do {
                if (strcmp(getcaller(), next->login)  ==  0)
                        return true;
        } while ((next = next->nextaccess));
d1589 2
a1590 2
        error("user %s not on the access list", getcaller());
        return false;
d1594 1
a1594 1
        int
d1596 1
a1596 1
        int lockflag, changed;
d1605 1
a1605 1
        int r, e;
d1607 23
a1629 23
        if (lockflag)
                if (changed) {
                        if (changed < 0)
                                return false;
                        putadmin(frewrite);
                        puttree(Head, frewrite);
                        aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
                        foutptr = frewrite;
                } else {
                        Ozclose(&frewrite);
                        seteid();
                        ignoreints();
                        r = un_link(newRCSfilename);
                        e = errno;
                        keepdirtemp(newRCSfilename);
                        restoreints();
                        setrid();
                        if (r != 0) {
                                enerror(e, RCSfilename);
                                return false;
                        }
                }
        return true;
d1632 1
a1632 1
        int
d1634 1
a1634 1
        int changed;
d1640 1
a1640 1
        int r, e;
d1642 24
a1665 24
        if (changed && !nerror) {
                if (finptr) {
                        fastcopy(finptr, frewrite);
                        Izclose(&finptr);
                }
                if (1 < RCSstat.st_nlink)
                        warn("breaking hard link to %s", RCSfilename);
                seteid();
                ignoreints();
                r = chnamemod(&frewrite, newRCSfilename, RCSfilename,
                        RCSstat.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH)
                );
                e = errno;
                keepdirtemp(newRCSfilename);
                restoreints();
                setrid();
                if (r != 0) {
                        enerror(e, RCSfilename);
                        error("saved in %s", newRCSfilename);
                        dirtempunlink();
                        return false;
                }
        }
        return true;
d1668 1
a1668 1
        void
d1670 1
a1670 1
        FILE *f;
d1672 2
a1673 2
        if (fflush(f) != 0)
                Oerror();
@


5.11.1.3
log
@Changed all checks for AMIGA to _AMIGA. This is more standard like
and helps future updates. Major patch cleanup on the way.
[Note: Added a VOID_CLOSEDIR define for this global change, too!]
@
text
@a38 3
 * Revision 5.11.1.2  1993/12/04  08:40:36  heinz
 * Removed the SAS/C create patch, as the problem has been fixed for 6.3.
 *
d160 1
a160 1
libId(editId, "$Id: rcsedit.c,v 5.11.1.2 1993/12/04 08:40:36 heinz Exp heinz $")
d1365 1
a1365 3
#ifdef _AMIGA
                /* AMIGA fix for delete before close! */

d1375 1
a1375 1
#endif /* _AMIGA */
@


5.11.1.4
log
@Support for the new keywords VER, AmigaREV, and AmigaProtection.
@
text
@a38 5
 * Revision 5.11.1.3  1993/12/18  16:12:22  heinz
 * Changed all checks for AMIGA to _AMIGA. This is more standard like
 * and helps future updates. Major patch cleanup on the way.
 * [Note: Added a VOID_CLOSEDIR define for this global change, too!]
 *
d163 1
a163 1
libId(editId, "$Id: rcsedit.c,v 5.11.1.3 1993/12/18 16:12:22 heinz Exp heinz $")
a813 6
#ifdef _AMIGA
/* We need this bufferfor a CBMVersion string increment. This might not be
   a very nice way to do all this. But it is easy for now. */

        char CBMbuf[BUFSIZ];
#endif /* _AMIGA */
a902 21
#ifdef _AMIGA
/* Only with CBMVersion, a newline end is allowed. We want
   to be able to specify C= style version strings without a trailing `$'.
*/

                              if(matchresult == CBMVersion)
                              {
                                  /* We keep the delimiting KDELIM for CBMVersion! */
                                  if(c == KDELIM)
                                  {
                                      *tp++ = c;
                                      if(tlim <= tp)
                                      {
                                          tp = bufenlarge(&keyval, &tlim);
                                      } /* if */
                                  } /* if */

                                  /* We are ok! */
                                  c = KDELIM;
                              } /* if */
#endif /* _AMIGA */
a909 266
#ifdef _AMIGA
/* This is really ugly. For Amiga keywords, we need to take their current
   value und set it in the delta unless there is already a value set. The
   ugly thing about it is that the delta is const. So we have to cast the
   pointer to non const for modifying the delta contents. Hmpfh.
*/
                        /* Terminate the string for sure. */
                        *tp = 0;
                        if(matchresult == CBMVersion ||
                           matchresult == CBMRevision ||
                           matchresult == CBMProtection)
                        {
                            char *vp = strchr(keyval.string + 1, VDELIM);
                            char **dp;
                            int incflag = delta->CBMVersionIncFlag;

                            if(vp)
                            {
                                vp++;

                                /* Skip leading space. */
                                while(*vp == ' ' || *vp == '\t')
                                {
                                    vp++;
                                } /* while */

                                if(*vp == 0)
                                {
                                    /* No entry, no nothing! */
                                    vp = NULL;
                                } /* if */
                            } /* if */

                            switch(matchresult)
                            {
                                case CBMVersion:
                                    dp = (char **)&delta->CBMVersion;
                                    if(!vp)
                                    {
                                        strcpy(CBMbuf, workfilename);
                                        strcat(CBMbuf, " 0.0  (xx.xx.xx)  $");

                                        vp = CBMbuf;

                                        /* Force expansion of the fragment */
                                        incflag = true;
                                    } /* if */

                                    if(incflag)
                                    {
                                        /* Ouch. Now it gets tricky.
                                           We don't use sscanf here to be sure of
                                           what we do. */
                                        const char *s;
                                        char *d;
                                        int ver, rev, l;
                                        int stcd_i(const char *, int *);

                                        s = vp;
                                        d = CBMbuf;

                                        /* Read the prg name */
                                        while(*s && *s != ' ' && *s != '\t')
                                        {
                                            *d++ = *s++;
                                        } /* while */

                                        /* Skip space after prg name */
                                        while(*s == ' ' || *s == '\t')
                                        {
                                            s++;
                                        } /* while */

                                        l = stcd_i(s, &ver);
                                        s += l;
                                        if(!l || *s++ != '.' || ver < 0)
                                        {
                                            break;
                                        } /* if */

                                        l = stcd_i(s, &rev);
                                        s += l;

                                        /* Skip space after ver.rev */
                                        while(*s == ' ' || *s == '\t')
                                        {
                                            s++;
                                        } /* while */

                                        if(!l || *s != '(' || rev < 0)
                                        {
                                            break;
                                        } /* if */

                                        /* Skip over to ')' and skip trailing space */
                                        while(*s && *s != ')')
                                        {
                                            s++;
                                        } /* while */

                                        if(*s == ')')
                                        {
                                            s++;
                                        } /* if */

                                        /* Skip space after date */
                                        while(*s == ' ' || *s == '\t')
                                        {
                                            s++;
                                        } /* while */

                                        sprintf(d, " %d.%d (", ver, rev + 1);
                                        d += strlen(d);

                                        /* We always insert the current date! */
                                        {
                                            time_t t;

                                            time(&t);

                                            l = strftime(d, 11, "%d.%m.%y) ", localtime(&t));
                                            d += l;
                                        }

                                        /* For VERSION FULL */
                                        strcpy(d, s);

                                        vp = CBMbuf;
                                    } /* if */
                                    break;
                                case CBMRevision:
                                    dp = (char **)&delta->CBMRevision;
                                    if(!vp)
                                    {
                                        strcpy(CBMbuf, "0");

                                        vp = CBMbuf;

                                        /* Force expansion of the fragment */
                                        incflag = true;
                                    } /* if */

                                    if(incflag)
                                    {
                                        /* Ouch. Now it gets tricky.
                                           We don't use sscanf here to be sure of
                                           what we do. */
                                        const char *s;
                                        char *d;
                                        int ver, rev, l;
                                        int stcd_i(const char *, int *);

                                        s = vp;
                                        d = CBMbuf;

                                        /* Skip to a digit */
                                        while(*s && !isdigit(*s))
                                        {
                                            *d++ = *s++;
                                        } /* while */

                                        l = stcd_i(s, &rev);
                                        s += l;

                                        if(!l || rev < 0)
                                        {
                                            break;
                                        } /* if */

                                        {
                                            char numbuf[12];

                                            sprintf(numbuf, "%d", rev + 1);
                                            l = strlen(numbuf);

                                            memmove(d + l, s, strlen(s) + 1);
                                            memmove(d, numbuf, l);
                                        }

                                        /* Trim off trailing space */
                                        l = strlen(CBMbuf);
                                        while(l &&
                                              (CBMbuf[l - 1] == ' ' ||
                                               CBMbuf[l - 1] == '\t'))
                                        {
                                            CBMbuf[--l] = 0;
                                        } /* while */

                                        vp = CBMbuf;
                                    } /* if */
                                    break;
                                case CBMProtection:
                                    dp = (char **)&delta->CBMProtection;
                                    if(!vp)
                                    {
                                        char *d = CBMbuf;

                                        if(RCSstat.st_mode & S_IPURE)
                                        {
                                            *d ++ = 'P';
                                        } /* if */

                                        if(RCSstat.st_mode & S_ISCRIPT)
                                        {
                                            *d ++ = 'S';
                                        } /* if */

                                        *d = 0;
                                    }
                                    else
                                    {
                                        char *s, *d;

                                        s = vp;

                                        /* Here we have the most ugly hack
                                           today. Not much I can do about it.
                                           Unless there can be more information
                                           of generic value kept per revision,
                                           I have to hack around. */
                                        RCSstat.st_mode &= ~(S_IPURE|S_ISCRIPT);
                                        d = CBMbuf;
                                        while(*d = *s++)
                                        {
                                            if(*d != ' ' && *d != '\t')
                                            {
                                                switch(*d)
                                                {
                                                    case 'P':
                                                    case 'p':
                                                        RCSstat.st_mode |= S_IPURE;
                                                        break;
                                                    case 'S':
                                                    case 's':
                                                        RCSstat.st_mode |= S_ISCRIPT;
                                                        break;
                                                    default:
                                                        break;
                                                } /* switch */
                                                d++;
                                            } /* if */
                                        } /* while */
                                    } /* if */

                                    if(!CBMbuf[0])
                                    {
                                        strcpy(CBMbuf, "--");
                                    } /* if */

                                    vp = CBMbuf;
                                    break;
                                default:
                                    dp = NULL;
                                    break;
                            } /* switch */

                            /* Do we have a value? */
                            if(dp)
                            {
                                /* Set the value in the delta.
                                   This is just for keyreplace below. */
                                *dp = vp;
                            } /* if */
                        } /* if */

#endif /* _AMIGA */
a1008 12
#ifdef _AMIGA
/* The Amiga keywords CBMVersion and CBMProtection! */
        case CBMVersion:
                aputs(delta->CBMVersion, out);
                break;
        case CBMRevision:
                aputs(delta->CBMRevision, out);
                break;
        case CBMProtection:
                aputs(delta->CBMProtection, out);
                break;
#endif /* _AMIGA */
a1012 21
#ifdef _AMIGA
/* Only with CBMVersion, a newline end is allowed. We want
   to be able to specify C= style version strings without a trailing `$'.
   So the trailing KDELIM is included in the version string. We don't have
   to add it here!
*/
                if(marker != CBMVersion)
                {
                    afputc(' ', out);
                    afputc(KDELIM, out);
                }
                else
                {
                    int l = strlen(delta->CBMVersion);

                    if(!l || delta->CBMVersion[l - 1] != KDELIM)
                    {
                        afputc('\n', out);
                    } /* if */
                } /* if */
#else
a1014 1
#endif /* _AMIGA */
@


5.11.1.5
log
@First version of Amiga keywords implemented. Ugly, but acceptable for now.
@
text
@a38 3
 * Revision 5.11.1.4  1994/02/12  19:20:21  heinz
 * Support for the new keywords VER, AmigaREV, and AmigaProtection.
 *
d168 1
a168 1
libId(editId, "$Id: rcsedit.c,v 5.11.1.4 1994/02/12 19:20:21 heinz Exp heinz $")
a171 10
#ifdef _AMIGA
/* We need a flag for ci that tells us when to increment
   our special revision numbers. This flag is in the delta structure to be
   specific about what delta to "increment".
   There is also a global variable to pass
   the string we create fromexpandline to keyreplace. I don't want to patch
   into the functions declarations. */

static char *AMIGA_specialstring;
#endif /*_AMIGA */
a951 1
                           matchresult == CBMDate ||
d955 2
a956 1
                            int incflag = delta->CBMRevIncFlag;
d978 1
d982 1
a982 1
                                        strcat(CBMbuf, " 0.0  (xx.xx.xx) $");
d1036 1
a1036 1
                                        /* Skip over to ')' and skip it, too */
d1047 6
d1062 1
a1062 1
                                            l = strftime(d, 11, "%d.%m.%y)", localtime(&t));
d1073 1
d1091 1
a1091 1
                                        int rev, l;
d1097 1
a1097 1
                                        /* Copy up to a digit */
a1132 81
                                case CBMDate:
                                    if(!vp)
                                    {
                                        strcpy(CBMbuf, "00.00.00");

                                        vp = CBMbuf;

                                        /* Force expansion of the fragment */
                                        incflag = true;
                                    } /* if */

                                    if(incflag)
                                    {
                                        /* Ouch. Now it gets tricky.
                                           We don't use sscanf here to be sure of
                                           what we do. */
                                        const char *s;
                                        char *d;
                                        int l;

                                        s = vp;
                                        d = CBMbuf;

                                        /* Copy up to a digit */
                                        while(*s && !isdigit(*s))
                                        {
                                            *d++ = *s++;
                                        } /* while */

                                        /* Skip over day digits */
                                        while(*s && isdigit(*s))
                                        {
                                            s++;
                                        } /* while */

                                        if(*s == '.')
                                        {
                                            s++;
                                        } /* if */

                                        /* Skip over month digits */
                                        while(*s && isdigit(*s))
                                        {
                                            s++;
                                        } /* while */

                                        if(*s == '.')
                                        {
                                            s++;
                                        } /* if */

                                        /* Skip over year digits */
                                        while(*s && isdigit(*s))
                                        {
                                            s++;
                                        } /* while */

                                        {
                                            char datebuf[12];
                                            time_t t;

                                            time(&t);

                                            l = strftime(datebuf, 11, "%d.%m.%y", localtime(&t));

                                            memmove(d + l, s, strlen(s) + 1);
                                            memmove(d, datebuf, l);
                                        }

                                        /* Trim off trailing space */
                                        l = strlen(CBMbuf);
                                        while(l &&
                                              (CBMbuf[l - 1] == ' ' ||
                                               CBMbuf[l - 1] == '\t'))
                                        {
                                            CBMbuf[--l] = 0;
                                        } /* while */

                                        vp = CBMbuf;
                                    } /* if */
                                    break;
d1134 1
d1194 1
a1194 1
                                    vp = NULL;
d1198 7
a1204 3
                            /* Set the value in the delta.
                               This is just for keyreplace below. */
                            AMIGA_specialstring = (vp) ? vp : "";
d1206 1
d1308 1
a1308 1
/* The Amiga keywords use only one special variable set in expandline! */
d1310 2
d1313 2
d1316 1
a1316 2
        case CBMDate:
                aputs(AMIGA_specialstring, out);
d1336 1
a1336 1
                    int l = strlen(AMIGA_specialstring);
d1338 1
a1338 1
                    if(!l || AMIGA_specialstring[l - 1] != KDELIM)
@


5.11.1.6
log
@Auto increment support for the new AMiga keywords should work better now.
Somme comment clarifications.
@
text
@a38 3
 * Revision 5.11.1.5  1994/02/14  19:29:45  heinz
 * First version of Amiga keywords implemented. Ugly, but acceptable for now.
 *
d171 1
a171 1
libId(editId, "$Id: rcsedit.c,v 5.11.1.5 1994/02/14 19:29:45 heinz Exp heinz $")
d177 3
a179 4
   our special revision numbers in what delta. */
struct hshentry *CBMRevIncDelta;

/* There is also a global variable to pass
d969 1
a969 1
                            int incflag = (delta == CBMRevIncDelta);
d1778 1
a1778 3
/* AMIGA fix for delete before close! Unlinking an open file is not
   possible with any non Unix file system I know. So this problem affects
   the Amiga, too! */
@
