111894Speter/* RCS stream editor */
211894Speter
311894Speter/******************************************************************************
49Sjkh *                       edits the input file according to a
59Sjkh *                       script from stdin, generated by diff -n
69Sjkh *                       performs keyword expansion
711894Speter ******************************************************************************
89Sjkh */
99Sjkh
1011894Speter/* Copyright 1982, 1988, 1989 Walter Tichy
1111894Speter   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
129Sjkh   Distributed under license by the Free Software Foundation, Inc.
139Sjkh
149SjkhThis file is part of RCS.
159Sjkh
169SjkhRCS is free software; you can redistribute it and/or modify
179Sjkhit under the terms of the GNU General Public License as published by
189Sjkhthe Free Software Foundation; either version 2, or (at your option)
199Sjkhany later version.
209Sjkh
219SjkhRCS is distributed in the hope that it will be useful,
229Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of
239SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
249SjkhGNU General Public License for more details.
259Sjkh
269SjkhYou should have received a copy of the GNU General Public License
2711894Speteralong with RCS; see the file COPYING.
2811894SpeterIf not, write to the Free Software Foundation,
2911894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
309Sjkh
319SjkhReport problems and direct all questions to:
329Sjkh
339Sjkh    rcs-bugs@cs.purdue.edu
349Sjkh
359Sjkh*/
369Sjkh
3711894Speter/*
3811894Speter * Revision 5.19  1995/06/16 06:19:24  eggert
3911894Speter * Update FSF address.
408858Srgrimes *
4111894Speter * Revision 5.18  1995/06/01 16:23:43  eggert
4211894Speter * (dirtpname): No longer external.
4311894Speter * (do_link): Simplify logic.
4411894Speter * (finisheditline, finishedit): Replace Iseek/Itell with what they stand for.
4511894Speter * (fopen_update_truncate): Replace `#if' with `if'.
4611894Speter * (keyreplace, makedirtemp): dirlen(x) -> basefilename(x)-x.
471494Srgrimes *
4811894Speter * (edit_string): Fix bug: if !large_memory, a bogus trailing `@' was output
4911894Speter * at the end of incomplete lines.
5011894Speter *
5111894Speter * (keyreplace): Do not assume that seeking backwards
5211894Speter * at the start of a file will fail; on some systems it succeeds.
5311894Speter * Convert C- and Pascal-style comment starts to ` *' in comment leader.
5411894Speter *
5511894Speter * (rcswriteopen): Use fdSafer to get safer file descriptor.
5611894Speter * Open RCS file with FOPEN_RB.
5711894Speter *
5811894Speter * (chnamemod): Work around bad_NFS_rename bug; don't ignore un_link result.
5911894Speter * Fall back on chmod if fchmod fails, since it might be ENOSYS.
6011894Speter *
6111894Speter * (aflush): Move to rcslex.c.
6211894Speter *
6311894Speter * Revision 5.17  1994/03/20 04:52:58  eggert
6411894Speter * Normally calculate the $Log prefix from context, not from RCS file.
6511894Speter * Move setmtime here from rcsutil.c.  Add ORCSerror.  Remove lint.
6611894Speter *
6711894Speter * Revision 5.16  1993/11/03 17:42:27  eggert
6811894Speter * Add -z.  Add Name keyword.  If bad_unlink, ignore errno when unlink fails.
6911894Speter * Escape white space, $, and \ in keyword string file names.
7011894Speter * Don't output 2 spaces between date and time after Log.
7111894Speter *
7211894Speter * Revision 5.15  1992/07/28  16:12:44  eggert
7311894Speter * Some hosts have readlink but not ELOOP.  Avoid `unsigned'.
7411894Speter * Preserve dates more systematically.  Statement macro names now end in _.
7511894Speter *
7611894Speter * Revision 5.14  1992/02/17  23:02:24  eggert
7711894Speter * Add -T support.
7811894Speter *
7911894Speter * Revision 5.13  1992/01/24  18:44:19  eggert
8011894Speter * Add support for bad_chmod_close, bad_creat0.
8111894Speter *
8211894Speter * Revision 5.12  1992/01/06  02:42:34  eggert
8311894Speter * Add setmode parameter to chnamemod.  addsymbol now reports changes.
8411894Speter * while (E) ; -> while (E) continue;
8511894Speter *
869Sjkh * Revision 5.11  1991/11/03  01:11:44  eggert
879Sjkh * Move the warning about link breaking to where they're actually being broken.
889Sjkh *
899Sjkh * Revision 5.10  1991/10/07  17:32:46  eggert
909Sjkh * Support piece tables even if !has_mmap.  Fix rare NFS bugs.
919Sjkh *
929Sjkh * Revision 5.9  1991/09/17  19:07:40  eggert
939Sjkh * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
949Sjkh *
959Sjkh * Revision 5.8  1991/08/19  03:13:55  eggert
969Sjkh * Add piece tables, NFS bug workarounds.  Catch odd filenames.  Tune.
979Sjkh *
989Sjkh * Revision 5.7  1991/04/21  11:58:21  eggert
999Sjkh * Fix errno bugs.  Add -x, RCSINIT, MS-DOS support.
1009Sjkh *
1019Sjkh * Revision 5.6  1991/02/25  07:12:40  eggert
1029Sjkh * Fix setuid bug.  Support new link behavior.  Work around broken "w+" fopen.
1039Sjkh *
1049Sjkh * Revision 5.5  1990/12/30  05:07:35  eggert
1059Sjkh * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL).
1069Sjkh *
1079Sjkh * Revision 5.4  1990/11/01  05:03:40  eggert
1089Sjkh * Permit arbitrary data in comment leaders.
1099Sjkh *
1109Sjkh * Revision 5.3  1990/09/11  02:41:13  eggert
1119Sjkh * Tune expandline().
1129Sjkh *
1139Sjkh * Revision 5.2  1990/09/04  08:02:21  eggert
1149Sjkh * Count RCS lines better.  Improve incomplete line handling.
1159Sjkh *
1169Sjkh * Revision 5.1  1990/08/29  07:13:56  eggert
1179Sjkh * Add -kkvl.
1189Sjkh * Fix bug when getting revisions to files ending in incomplete lines.
1199Sjkh * Fix bug in comment leader expansion.
1209Sjkh *
1219Sjkh * Revision 5.0  1990/08/22  08:12:47  eggert
1229Sjkh * Don't require final newline.
1239Sjkh * Don't append "checked in with -k by " to logs,
1249Sjkh * so that checking in a program with -k doesn't change it.
1259Sjkh * Don't generate trailing white space for empty comment leader.
1269Sjkh * Remove compile-time limits; use malloc instead.  Add -k, -V.
1279Sjkh * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
1289Sjkh * Ansify and Posixate.  Check diff's output.
1299Sjkh *
1309Sjkh * Revision 4.8  89/05/01  15:12:35  narten
1319Sjkh * changed copyright header to reflect current distribution rules
1328858Srgrimes *
1339Sjkh * Revision 4.7  88/11/08  13:54:14  narten
1349Sjkh * misplaced semicolon caused infinite loop
1358858Srgrimes *
1369Sjkh * Revision 4.6  88/08/09  19:12:45  eggert
1379Sjkh * Shrink stdio code size; allow cc -R.
1388858Srgrimes *
1399Sjkh * Revision 4.5  87/12/18  11:38:46  narten
1409Sjkh * Changes from the 43. version. Don't know the significance of the
1419Sjkh * first change involving "rewind". Also, additional "lint" cleanup.
1429Sjkh * (Guy Harris)
1438858Srgrimes *
1449Sjkh * Revision 4.4  87/10/18  10:32:21  narten
1459Sjkh * Updating version numbers. Changes relative to version 1.1 actually
1469Sjkh * relative to 4.1
1478858Srgrimes *
1489Sjkh * Revision 1.4  87/09/24  13:59:29  narten
1498858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf
1509Sjkh * warnings)
1518858Srgrimes *
1529Sjkh * Revision 1.3  87/09/15  16:39:39  shepler
1539Sjkh * added an initializatin of the variables editline and linecorr
1549Sjkh * this will be done each time a file is processed.
1559Sjkh * (there was an obscure bug where if co was used to retrieve multiple files
1569Sjkh *  it would dump)
1579Sjkh * fix attributed to  Roy Morris @FileNet Corp ...!felix!roy
1588858Srgrimes *
1599Sjkh * Revision 1.2  87/03/27  14:22:17  jenkins
1609Sjkh * Port to suns
1618858Srgrimes *
1629Sjkh * Revision 4.1  83/05/12  13:10:30  wft
1639Sjkh * Added new markers Id and RCSfile; added locker to Header and Id.
1649Sjkh * Overhauled expandline completely() (problem with $01234567890123456789@).
1659Sjkh * Moved trymatch() and marker table to rcskeys.c.
1668858Srgrimes *
1679Sjkh * Revision 3.7  83/05/12  13:04:39  wft
1689Sjkh * Added retry to expandline to resume after failed match which ended in $.
1699Sjkh * Fixed truncation problem for $19chars followed by@@.
1709Sjkh * Log no longer expands full path of RCS file.
1718858Srgrimes *
1729Sjkh * Revision 3.6  83/05/11  16:06:30  wft
1739Sjkh * added retry to expandline to resume after failed match which ended in $.
1749Sjkh * Fixed truncation problem for $19chars followed by@@.
1758858Srgrimes *
1769Sjkh * Revision 3.5  82/12/04  13:20:56  wft
1779Sjkh * Added expansion of keyword Locker.
1789Sjkh *
1799Sjkh * Revision 3.4  82/12/03  12:26:54  wft
1809Sjkh * Added line number correction in case editing does not start at the
1819Sjkh * beginning of the file.
1829Sjkh * Changed keyword expansion to always print a space before closing KDELIM;
1839Sjkh * Expansion for Header shortened.
1849Sjkh *
1859Sjkh * Revision 3.3  82/11/14  14:49:30  wft
1869Sjkh * removed Suffix from keyword expansion. Replaced fclose with ffclose.
1879Sjkh * keyreplace() gets log message from delta, not from curlogmsg.
1889Sjkh * fixed expression overflow in while(c=putc(GETC....
1899Sjkh * checked nil printing.
1909Sjkh *
1919Sjkh * Revision 3.2  82/10/18  21:13:39  wft
1929Sjkh * I added checks for write errors during the co process, and renamed
1939Sjkh * expandstring() to xpandstring().
1949Sjkh *
1959Sjkh * Revision 3.1  82/10/13  15:52:55  wft
1969Sjkh * changed type of result of getc() from char to int.
1979Sjkh * made keyword expansion loop in expandline() portable to machines
1989Sjkh * without sign-extension.
1999Sjkh */
2009Sjkh
2019Sjkh
2029Sjkh#include "rcsbase.h"
2039Sjkh
20450472SpeterlibId(editId, "$FreeBSD$")
2059Sjkh
20611894Speterstatic void editEndsPrematurely P((void)) exiting;
20711894Speterstatic void editLineNumberOverflow P((void)) exiting;
20811894Speterstatic void escape_string P((FILE*,char const*));
20911894Speterstatic void keyreplace P((enum markers,struct hshentry const*,int,RILE*,FILE*,int));
2109Sjkh
2119SjkhFILE *fcopy;		 /* result file descriptor			    */
21211894Speterchar const *resultname;	 /* result pathname				    */
2139Sjkhint locker_expansion;	 /* should the locker name be appended to Id val?   */
2149Sjkh#if !large_memory
2159Sjkh	static RILE *fedit; /* edit file descriptor */
21611894Speter	static char const *editname; /* edit pathname */
2179Sjkh#endif
21811894Speterstatic long editline; /* edit line counter; #lines before cursor   */
2199Sjkhstatic long linecorr; /* #adds - #deletes in each edit run.		    */
2209Sjkh               /*used to correct editline in case file is not rewound after */
2219Sjkh               /* applying one delta                                        */
2229Sjkh
22311894Speter/* indexes into dirtpname */
22411894Speter#define lockdirtp_index 0
22511894Speter#define newRCSdirtp_index bad_creat0
22611894Speter#define newworkdirtp_index (newRCSdirtp_index+1)
22711894Speter#define DIRTEMPNAMES (newworkdirtp_index + 1)
22811894Speter
2299Sjkhenum maker {notmade, real, effective};
23011894Speterstatic struct buf dirtpname[DIRTEMPNAMES];	/* unlink these when done */
23111894Speterstatic enum maker volatile dirtpmaker[DIRTEMPNAMES];	/* if these are set */
23211894Speter#define lockname (dirtpname[lockdirtp_index].string)
23311894Speter#define newRCSname (dirtpname[newRCSdirtp_index].string)
2349Sjkh
2359Sjkh
2369Sjkh#if has_NFS || bad_unlink
2379Sjkh	int
2389Sjkhun_link(s)
2399Sjkh	char const *s;
2409Sjkh/*
2419Sjkh * Remove S, even if it is unwritable.
2429Sjkh * Ignore unlink() ENOENT failures; NFS generates bogus ones.
2439Sjkh */
2449Sjkh{
2459Sjkh#	if bad_unlink
2469Sjkh		if (unlink(s) == 0)
2479Sjkh			return 0;
24811894Speter		else {
24911894Speter			int e = errno;
25011894Speter			/*
25111894Speter			* Forge ahead even if errno == ENOENT; some completely
25211894Speter			* brain-damaged hosts (e.g. PCTCP 2.2) yield ENOENT
25311894Speter			* even for existing unwritable files.
25411894Speter			*/
25511894Speter			if (chmod(s, S_IWUSR) != 0) {
25611894Speter				errno = e;
25711894Speter				return -1;
25811894Speter			}
2599Sjkh		}
2609Sjkh#	endif
2619Sjkh#	if has_NFS
2629Sjkh		return unlink(s)==0 || errno==ENOENT  ?  0  :  -1;
2639Sjkh#	else
2649Sjkh		return unlink(s);
2659Sjkh#	endif
2669Sjkh}
2679Sjkh#endif
2689Sjkh
2699Sjkh#if !has_rename
2709Sjkh#  if !has_NFS
2719Sjkh#	define do_link(s,t) link(s,t)
2729Sjkh#  else
27311894Speter	static int do_link P((char const*,char const*));
2749Sjkh	static int
2759Sjkhdo_link(s, t)
2769Sjkh	char const *s, *t;
2779Sjkh/* Link S to T, ignoring bogus EEXIST problems due to NFS failures.  */
2789Sjkh{
27911894Speter	int r = link(s, t);
2809Sjkh
28111894Speter	if (r != 0  &&  errno == EEXIST) {
28211894Speter		struct stat sb, tb;
28311894Speter		if (
28411894Speter		    stat(s, &sb) == 0  &&
28511894Speter		    stat(t, &tb) == 0  &&
28611894Speter		    same_file(sb, tb, 0)
28711894Speter		)
28811894Speter			r = 0;
28911894Speter		errno = EEXIST;
29011894Speter	}
29111894Speter	return r;
2929Sjkh}
2939Sjkh#  endif
2949Sjkh#endif
2959Sjkh
2969Sjkh
29711894Speter	static void
2989SjkheditEndsPrematurely()
2999Sjkh{
3009Sjkh	fatserror("edit script ends prematurely");
3019Sjkh}
3029Sjkh
30311894Speter	static void
3049SjkheditLineNumberOverflow()
3059Sjkh{
3069Sjkh	fatserror("edit script refers to line past end of file");
3079Sjkh}
3089Sjkh
3099Sjkh
3109Sjkh#if large_memory
3119Sjkh
3129Sjkh#if has_memmove
3139Sjkh#	define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
3149Sjkh#else
31511894Speter	static void movelines P((Iptr_type*,Iptr_type const*,long));
3169Sjkh	static void
3179Sjkhmovelines(s1, s2, n)
3189Sjkh	register Iptr_type *s1;
3199Sjkh	register Iptr_type const *s2;
32011894Speter	register long n;
3219Sjkh{
3229Sjkh	if (s1 < s2)
3239Sjkh		do {
3249Sjkh			*s1++ = *s2++;
3259Sjkh		} while (--n);
3269Sjkh	else {
3279Sjkh		s1 += n;
3289Sjkh		s2 += n;
3299Sjkh		do {
3309Sjkh			*--s1 = *--s2;
3319Sjkh		} while (--n);
3329Sjkh	}
3339Sjkh}
3349Sjkh#endif
3359Sjkh
33611894Speterstatic void deletelines P((long,long));
33711894Speterstatic void finisheditline P((RILE*,FILE*,Iptr_type,struct hshentry const*));
33811894Speterstatic void insertline P((long,Iptr_type));
33911894Speterstatic void snapshotline P((FILE*,Iptr_type));
34011894Speter
3419Sjkh/*
3429Sjkh * `line' contains pointers to the lines in the currently `edited' file.
3439Sjkh * It is a 0-origin array that represents linelim-gapsize lines.
34411894Speter * line[0 .. gap-1] and line[gap+gapsize .. linelim-1] hold pointers to lines.
34511894Speter * line[gap .. gap+gapsize-1] contains garbage.
3469Sjkh *
3479Sjkh * Any @s in lines are duplicated.
3489Sjkh * Lines are terminated by \n, or (for a last partial line only) by single @.
3499Sjkh */
3509Sjkhstatic Iptr_type *line;
35111894Speterstatic size_t gap, gapsize, linelim;
3529Sjkh
3539Sjkh	static void
3549Sjkhinsertline(n, l)
35511894Speter	long n;
3569Sjkh	Iptr_type l;
3579Sjkh/* Before line N, insert line L.  N is 0-origin.  */
3589Sjkh{
3599Sjkh	if (linelim-gapsize < n)
3609Sjkh	    editLineNumberOverflow();
3619Sjkh	if (!gapsize)
3629Sjkh	    line =
3639Sjkh		!linelim ?
3649Sjkh			tnalloc(Iptr_type, linelim = gapsize = 1024)
3659Sjkh		: (
3669Sjkh			gap = gapsize = linelim,
3679Sjkh			trealloc(Iptr_type, line, linelim <<= 1)
3689Sjkh		);
3699Sjkh	if (n < gap)
3709Sjkh	    movelines(line+n+gapsize, line+n, gap-n);
3719Sjkh	else if (gap < n)
3729Sjkh	    movelines(line+gap, line+gap+gapsize, n-gap);
3739Sjkh
3749Sjkh	line[n] = l;
3759Sjkh	gap = n + 1;
3769Sjkh	gapsize--;
3779Sjkh}
3789Sjkh
3799Sjkh	static void
3809Sjkhdeletelines(n, nlines)
38111894Speter	long n, nlines;
3829Sjkh/* Delete lines N through N+NLINES-1.  N is 0-origin.  */
3839Sjkh{
38411894Speter	long l = n + nlines;
3859Sjkh	if (linelim-gapsize < l  ||  l < n)
3869Sjkh	    editLineNumberOverflow();
3879Sjkh	if (l < gap)
3889Sjkh	    movelines(line+l+gapsize, line+l, gap-l);
3899Sjkh	else if (gap < n)
3909Sjkh	    movelines(line+gap, line+gap+gapsize, n-gap);
3919Sjkh
3929Sjkh	gap = n;
3939Sjkh	gapsize += nlines;
3949Sjkh}
3959Sjkh
3969Sjkh	static void
3979Sjkhsnapshotline(f, l)
3989Sjkh	register FILE *f;
3999Sjkh	register Iptr_type l;
4009Sjkh{
4019Sjkh	register int c;
4029Sjkh	do {
4039Sjkh		if ((c = *l++) == SDELIM  &&  *l++ != SDELIM)
4049Sjkh			return;
40511894Speter		aputc_(c, f)
4069Sjkh	} while (c != '\n');
4079Sjkh}
4089Sjkh
4099Sjkh	void
4109Sjkhsnapshotedit(f)
4119Sjkh	FILE *f;
4129Sjkh/* Copy the current state of the edits to F.  */
4139Sjkh{
4149Sjkh	register Iptr_type *p, *lim, *l=line;
4159Sjkh	for (p=l, lim=l+gap;  p<lim;  )
4169Sjkh		snapshotline(f, *p++);
4179Sjkh	for (p+=gapsize, lim=l+linelim;  p<lim;  )
4189Sjkh		snapshotline(f, *p++);
4199Sjkh}
4209Sjkh
4219Sjkh	static void
4229Sjkhfinisheditline(fin, fout, l, delta)
4239Sjkh	RILE *fin;
4249Sjkh	FILE *fout;
4259Sjkh	Iptr_type l;
4269Sjkh	struct hshentry const *delta;
4279Sjkh{
42811894Speter	fin->ptr = l;
42911894Speter	if (expandline(fin, fout, delta, true, (FILE*)0, true)  <  0)
4309Sjkh		faterror("finisheditline internal error");
4319Sjkh}
4329Sjkh
4339Sjkh	void
4349Sjkhfinishedit(delta, outfile, done)
4359Sjkh	struct hshentry const *delta;
4369Sjkh	FILE *outfile;
4379Sjkh	int done;
4389Sjkh/*
4399Sjkh * Doing expansion if DELTA is set, output the state of the edits to OUTFILE.
4409Sjkh * But do nothing unless DONE is set (which means we are on the last pass).
4419Sjkh */
4429Sjkh{
4439Sjkh	if (done) {
4449Sjkh		openfcopy(outfile);
4459Sjkh		outfile = fcopy;
4469Sjkh		if (!delta)
4479Sjkh			snapshotedit(outfile);
4489Sjkh		else {
4499Sjkh			register Iptr_type *p, *lim, *l = line;
4509Sjkh			register RILE *fin = finptr;
45111894Speter			Iptr_type here = fin->ptr;
4529Sjkh			for (p=l, lim=l+gap;  p<lim;  )
4539Sjkh				finisheditline(fin, outfile, *p++, delta);
4549Sjkh			for (p+=gapsize, lim=l+linelim;  p<lim;  )
4559Sjkh				finisheditline(fin, outfile, *p++, delta);
45611894Speter			fin->ptr = here;
4579Sjkh		}
4589Sjkh	}
4599Sjkh}
4609Sjkh
46111894Speter/* Open a temporary NAME for output, truncating any previous contents.  */
46211894Speter#   define fopen_update_truncate(name) fopenSafer(name, FOPEN_W_WORK)
4639Sjkh#else /* !large_memory */
46411894Speter    static FILE * fopen_update_truncate P((char const*));
4659Sjkh    static FILE *
46611894Speterfopen_update_truncate(name)
46711894Speter    char const *name;
4689Sjkh{
46911894Speter	if (bad_fopen_wplus  &&  un_link(name) != 0)
47011894Speter		efaterror(name);
47111894Speter	return fopenSafer(name, FOPEN_WPLUS_WORK);
4729Sjkh}
4739Sjkh#endif
4749Sjkh
4759Sjkh
4769Sjkh	void
4779Sjkhopenfcopy(f)
4789Sjkh	FILE *f;
4799Sjkh{
4809Sjkh	if (!(fcopy = f)) {
48111894Speter		if (!resultname)
48211894Speter			resultname = maketemp(2);
48311894Speter		if (!(fcopy = fopen_update_truncate(resultname)))
48411894Speter			efaterror(resultname);
4859Sjkh	}
4869Sjkh}
4879Sjkh
4889Sjkh
4899Sjkh#if !large_memory
4909Sjkh
49111894Speter	static void swapeditfiles P((FILE*));
4929Sjkh	static void
4939Sjkhswapeditfiles(outfile)
4949Sjkh	FILE *outfile;
49511894Speter/* Function: swaps resultname and editname, assigns fedit=fcopy,
4969Sjkh * and rewinds fedit for reading.  Set fcopy to outfile if nonnull;
49711894Speter * otherwise, set fcopy to be resultname opened for reading and writing.
4989Sjkh */
4999Sjkh{
5009Sjkh	char const *tmpptr;
5019Sjkh
5029Sjkh	editline = 0;  linecorr = 0;
50311894Speter	Orewind(fcopy);
5049Sjkh	fedit = fcopy;
50511894Speter	tmpptr=editname; editname=resultname; resultname=tmpptr;
5069Sjkh	openfcopy(outfile);
5079Sjkh}
5089Sjkh
5099Sjkh	void
5109Sjkhsnapshotedit(f)
5119Sjkh	FILE *f;
5129Sjkh/* Copy the current state of the edits to F.  */
5139Sjkh{
51411894Speter	finishedit((struct hshentry *)0, (FILE*)0, false);
5159Sjkh	fastcopy(fedit, f);
5169Sjkh	Irewind(fedit);
5179Sjkh}
5189Sjkh
5199Sjkh	void
5209Sjkhfinishedit(delta, outfile, done)
5219Sjkh	struct hshentry const *delta;
5229Sjkh	FILE *outfile;
5239Sjkh	int done;
5249Sjkh/* copy the rest of the edit file and close it (if it exists).
52511894Speter * if delta, perform keyword substitution at the same time.
5269Sjkh * If DONE is set, we are finishing the last pass.
5279Sjkh */
5289Sjkh{
5299Sjkh	register RILE *fe;
5309Sjkh	register FILE *fc;
5319Sjkh
5329Sjkh	fe = fedit;
5339Sjkh	if (fe) {
5349Sjkh		fc = fcopy;
53511894Speter		if (delta) {
53611894Speter			while (1 < expandline(fe,fc,delta,false,(FILE*)0,true))
5379Sjkh				;
5389Sjkh                } else {
5399Sjkh			fastcopy(fe,fc);
5409Sjkh                }
5419Sjkh		Ifclose(fe);
5429Sjkh        }
5439Sjkh	if (!done)
5449Sjkh		swapeditfiles(outfile);
5459Sjkh}
5469Sjkh#endif
5479Sjkh
5489Sjkh
5499Sjkh
5509Sjkh#if large_memory
5519Sjkh#	define copylines(upto,delta) (editline = (upto))
5529Sjkh#else
55311894Speter	static void copylines P((long,struct hshentry const*));
5549Sjkh	static void
55511894Spetercopylines(upto, delta)
55611894Speter	register long upto;
5579Sjkh	struct hshentry const *delta;
5589Sjkh/*
5599Sjkh * Copy input lines editline+1..upto from fedit to fcopy.
56011894Speter * If delta, keyword expansion is done simultaneously.
5619Sjkh * editline is updated. Rewinds a file only if necessary.
5629Sjkh */
5639Sjkh{
5649Sjkh	register int c;
5659Sjkh	declarecache;
5669Sjkh	register FILE *fc;
5679Sjkh	register RILE *fe;
5689Sjkh
5699Sjkh	if (upto < editline) {
5709Sjkh                /* swap files */
57111894Speter		finishedit((struct hshentry *)0, (FILE*)0, false);
5729Sjkh                /* assumes edit only during last pass, from the beginning*/
5739Sjkh        }
5749Sjkh	fe = fedit;
5759Sjkh	fc = fcopy;
5769Sjkh	if (editline < upto)
5779Sjkh	    if (delta)
5789Sjkh		do {
57911894Speter		    if (expandline(fe,fc,delta,false,(FILE*)0,true) <= 1)
58011894Speter			editLineNumberOverflow();
5819Sjkh		} while (++editline < upto);
5829Sjkh	    else {
5839Sjkh		setupcache(fe); cache(fe);
5849Sjkh		do {
5859Sjkh			do {
58611894Speter				cachegeteof_(c, editLineNumberOverflow();)
58711894Speter				aputc_(c, fc)
5889Sjkh			} while (c != '\n');
5899Sjkh		} while (++editline < upto);
5909Sjkh		uncache(fe);
5919Sjkh	    }
5929Sjkh}
5939Sjkh#endif
5949Sjkh
5959Sjkh
5969Sjkh
5979Sjkh	void
5989Sjkhxpandstring(delta)
5999Sjkh	struct hshentry const *delta;
6009Sjkh/* Function: Reads a string terminated by SDELIM from finptr and writes it
6019Sjkh * to fcopy. Double SDELIM is replaced with single SDELIM.
6029Sjkh * Keyword expansion is performed with data from delta.
6039Sjkh * If foutptr is nonnull, the string is also copied unchanged to foutptr.
6049Sjkh */
6059Sjkh{
60611894Speter	while (1 < expandline(finptr,fcopy,delta,true,foutptr,true))
60711894Speter		continue;
6089Sjkh}
6099Sjkh
6109Sjkh
6119Sjkh	void
6129Sjkhcopystring()
6139Sjkh/* Function: copies a string terminated with a single SDELIM from finptr to
6149Sjkh * fcopy, replacing all double SDELIM with a single SDELIM.
6159Sjkh * If foutptr is nonnull, the string also copied unchanged to foutptr.
6169Sjkh * editline is incremented by the number of lines copied.
6179Sjkh * Assumption: next character read is first string character.
6189Sjkh */
6199Sjkh{	register c;
6209Sjkh	declarecache;
6219Sjkh	register FILE *frew, *fcop;
6229Sjkh	register int amidline;
6239Sjkh	register RILE *fin;
6249Sjkh
6259Sjkh	fin = finptr;
6269Sjkh	setupcache(fin); cache(fin);
6279Sjkh	frew = foutptr;
6289Sjkh	fcop = fcopy;
6299Sjkh	amidline = false;
6309Sjkh	for (;;) {
63111894Speter		GETC_(frew,c)
6329Sjkh		switch (c) {
6339Sjkh		    case '\n':
6349Sjkh			++editline;
6359Sjkh			++rcsline;
6369Sjkh			amidline = false;
6379Sjkh			break;
6389Sjkh		    case SDELIM:
63911894Speter			GETC_(frew,c)
6409Sjkh			if (c != SDELIM) {
6419Sjkh				/* end of string */
6429Sjkh				nextc = c;
6439Sjkh				editline += amidline;
6449Sjkh				uncache(fin);
6459Sjkh				return;
6469Sjkh			}
6479Sjkh			/* fall into */
6489Sjkh		    default:
6499Sjkh			amidline = true;
6509Sjkh			break;
6519Sjkh                }
65211894Speter		aputc_(c,fcop)
6539Sjkh        }
6549Sjkh}
6559Sjkh
6569Sjkh
6579Sjkh	void
6589Sjkhenterstring()
6599Sjkh/* Like copystring, except the string is put into the edit data structure.  */
6609Sjkh{
6619Sjkh#if !large_memory
66211894Speter	editname = 0;
6639Sjkh	fedit = 0;
6649Sjkh	editline = linecorr = 0;
66511894Speter	resultname = maketemp(1);
66611894Speter	if (!(fcopy = fopen_update_truncate(resultname)))
66711894Speter		efaterror(resultname);
6689Sjkh	copystring();
6699Sjkh#else
6709Sjkh	register int c;
6719Sjkh	declarecache;
6729Sjkh	register FILE *frew;
67311894Speter	register long e, oe;
6749Sjkh	register int amidline, oamidline;
6759Sjkh	register Iptr_type optr;
6769Sjkh	register RILE *fin;
6779Sjkh
6789Sjkh	e = 0;
6799Sjkh	gap = 0;
6809Sjkh	gapsize = linelim;
6819Sjkh	fin = finptr;
6829Sjkh	setupcache(fin); cache(fin);
6839Sjkh	advise_access(fin, MADV_NORMAL);
6849Sjkh	frew = foutptr;
6859Sjkh	amidline = false;
6869Sjkh	for (;;) {
68711894Speter		optr = cacheptr();
68811894Speter		GETC_(frew,c)
6899Sjkh		oamidline = amidline;
6909Sjkh		oe = e;
6919Sjkh		switch (c) {
6929Sjkh		    case '\n':
6939Sjkh			++e;
6949Sjkh			++rcsline;
6959Sjkh			amidline = false;
6969Sjkh			break;
6979Sjkh		    case SDELIM:
69811894Speter			GETC_(frew,c)
6999Sjkh			if (c != SDELIM) {
7009Sjkh				/* end of string */
7019Sjkh				nextc = c;
7029Sjkh				editline = e + amidline;
7039Sjkh				linecorr = 0;
7049Sjkh				uncache(fin);
7059Sjkh				return;
7069Sjkh			}
7079Sjkh			/* fall into */
7089Sjkh		    default:
7099Sjkh			amidline = true;
7109Sjkh			break;
7119Sjkh		}
7129Sjkh		if (!oamidline)
7139Sjkh			insertline(oe, optr);
7149Sjkh	}
7159Sjkh#endif
7169Sjkh}
7179Sjkh
7189Sjkh
7199Sjkh
7209Sjkh
7219Sjkh	void
7229Sjkh#if large_memory
7239Sjkhedit_string()
7249Sjkh#else
7259Sjkh  editstring(delta)
7269Sjkh	struct hshentry const *delta;
7279Sjkh#endif
7289Sjkh/*
7299Sjkh * Read an edit script from finptr and applies it to the edit file.
7309Sjkh#if !large_memory
7319Sjkh * The result is written to fcopy.
73211894Speter * If delta, keyword expansion is performed simultaneously.
7339Sjkh * If running out of lines in fedit, fedit and fcopy are swapped.
73411894Speter * editname is the name of the file that goes with fedit.
7359Sjkh#endif
7369Sjkh * If foutptr is set, the edit script is also copied verbatim to foutptr.
7379Sjkh * Assumes that all these files are open.
73811894Speter * resultname is the name of the file that goes with fcopy.
7399Sjkh * Assumes the next input character from finptr is the first character of
7409Sjkh * the edit script. Resets nextc on exit.
7419Sjkh */
7429Sjkh{
7439Sjkh        int ed; /* editor command */
7449Sjkh        register int c;
7459Sjkh	declarecache;
7469Sjkh	register FILE *frew;
7479Sjkh#	if !large_memory
7489Sjkh		register FILE *f;
74911894Speter		long line_lim = LONG_MAX;
7509Sjkh		register RILE *fe;
7519Sjkh#	endif
75211894Speter	register long i;
7539Sjkh	register RILE *fin;
7549Sjkh#	if large_memory
75511894Speter		register long j;
7569Sjkh#	endif
7579Sjkh	struct diffcmd dc;
7589Sjkh
7599Sjkh        editline += linecorr; linecorr=0; /*correct line number*/
7609Sjkh	frew = foutptr;
7619Sjkh	fin = finptr;
7629Sjkh	setupcache(fin);
7639Sjkh	initdiffcmd(&dc);
7649Sjkh	while (0  <=  (ed = getdiffcmd(fin,true,frew,&dc)))
7659Sjkh#if !large_memory
7669Sjkh		if (line_lim <= dc.line1)
7679Sjkh			editLineNumberOverflow();
7689Sjkh		else
7699Sjkh#endif
7709Sjkh		if (!ed) {
7719Sjkh			copylines(dc.line1-1, delta);
7729Sjkh                        /* skip over unwanted lines */
7739Sjkh			i = dc.nlines;
7749Sjkh			linecorr -= i;
7759Sjkh			editline += i;
7769Sjkh#			if large_memory
7779Sjkh			    deletelines(editline+linecorr, i);
7789Sjkh#			else
7799Sjkh			    fe = fedit;
7809Sjkh			    do {
7819Sjkh                                /*skip next line*/
7829Sjkh				do {
78311894Speter				    Igeteof_(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } )
7849Sjkh				} while (c != '\n');
7859Sjkh			    } while (--i);
7869Sjkh#			endif
7879Sjkh		} else {
78811894Speter			/* Copy lines without deleting any.  */
78911894Speter			copylines(dc.line1, delta);
7909Sjkh			i = dc.nlines;
7919Sjkh#			if large_memory
7929Sjkh				j = editline+linecorr;
7939Sjkh#			endif
7949Sjkh			linecorr += i;
7959Sjkh#if !large_memory
7969Sjkh			f = fcopy;
7979Sjkh			if (delta)
7989Sjkh			    do {
79911894Speter				switch (expandline(fin,f,delta,true,frew,true)){
8009Sjkh				    case 0: case 1:
8019Sjkh					if (i==1)
8029Sjkh					    return;
8039Sjkh					/* fall into */
8049Sjkh				    case -1:
8059Sjkh					editEndsPrematurely();
8069Sjkh				}
8079Sjkh			    } while (--i);
8089Sjkh			else
8099Sjkh#endif
8109Sjkh			{
8119Sjkh			    cache(fin);
8129Sjkh			    do {
8139Sjkh#				if large_memory
81411894Speter				    insertline(j++, cacheptr());
8159Sjkh#				endif
8169Sjkh				for (;;) {
81711894Speter				    GETC_(frew, c)
8189Sjkh				    if (c==SDELIM) {
81911894Speter					GETC_(frew, c)
8209Sjkh					if (c!=SDELIM) {
8219Sjkh					    if (--i)
8229Sjkh						editEndsPrematurely();
8239Sjkh					    nextc = c;
8249Sjkh					    uncache(fin);
8259Sjkh					    return;
8269Sjkh					}
8279Sjkh				    }
82811894Speter#				    if !large_memory
82911894Speter					aputc_(c, f)
83011894Speter#				    endif
83111894Speter				    if (c == '\n')
83211894Speter					break;
8339Sjkh				}
8349Sjkh				++rcsline;
8359Sjkh			    } while (--i);
8369Sjkh			    uncache(fin);
8379Sjkh			}
8389Sjkh                }
8399Sjkh}
8409Sjkh
8419Sjkh
8429Sjkh
8439Sjkh/* The rest is for keyword expansion */
8449Sjkh
8459Sjkh
8469Sjkh
8479Sjkh	int
84811894Speterexpandline(infile, outfile, delta, delimstuffed, frewfile, dolog)
8499Sjkh	RILE *infile;
8509Sjkh	FILE *outfile, *frewfile;
8519Sjkh	struct hshentry const *delta;
85211894Speter	int delimstuffed, dolog;
8539Sjkh/*
8549Sjkh * Read a line from INFILE and write it to OUTFILE.
85511894Speter * Do keyword expansion with data from DELTA.
8569Sjkh * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
8579Sjkh * If FREWFILE is set, copy the line unchanged to FREWFILE.
8589Sjkh * DELIMSTUFFED must be true if FREWFILE is set.
85911894Speter * Append revision history to log only if DOLOG is set.
8609Sjkh * Yields -1 if no data is copied, 0 if an incomplete line is copied,
8619Sjkh * 2 if a complete line is copied; adds 1 to yield if expansion occurred.
8629Sjkh */
8639Sjkh{
8649Sjkh	register c;
8659Sjkh	declarecache;
8669Sjkh	register FILE *out, *frew;
8679Sjkh	register char * tp;
8689Sjkh	register int e, ds, r;
8699Sjkh	char const *tlim;
8709Sjkh	static struct buf keyval;
8719Sjkh        enum markers matchresult;
8729Sjkh
8739Sjkh	setupcache(infile); cache(infile);
8749Sjkh	out = outfile;
8759Sjkh	frew = frewfile;
8769Sjkh	ds = delimstuffed;
8779Sjkh	bufalloc(&keyval, keylength+3);
8789Sjkh	e = 0;
8799Sjkh	r = -1;
8809Sjkh
8819Sjkh        for (;;) {
88211894Speter	    if (ds)
88311894Speter		GETC_(frew, c)
88411894Speter	    else
88511894Speter		cachegeteof_(c, goto uncache_exit;)
8869Sjkh	    for (;;) {
8879Sjkh		switch (c) {
8889Sjkh		    case SDELIM:
8899Sjkh			if (ds) {
89011894Speter			    GETC_(frew, c)
8919Sjkh			    if (c != SDELIM) {
8929Sjkh                                /* end of string */
8939Sjkh                                nextc=c;
8949Sjkh				goto uncache_exit;
8959Sjkh			    }
8969Sjkh			}
8979Sjkh			/* fall into */
8989Sjkh		    default:
89911894Speter			aputc_(c,out)
9009Sjkh			r = 0;
9019Sjkh			break;
9029Sjkh
9039Sjkh		    case '\n':
9049Sjkh			rcsline += ds;
90511894Speter			aputc_(c,out)
9069Sjkh			r = 2;
9079Sjkh			goto uncache_exit;
9089Sjkh
9099Sjkh		    case KDELIM:
9109Sjkh			r = 0;
9119Sjkh                        /* check for keyword */
9129Sjkh                        /* first, copy a long enough string into keystring */
9139Sjkh			tp = keyval.string;
9149Sjkh			*tp++ = KDELIM;
9159Sjkh			for (;;) {
91611894Speter			    if (ds)
91711894Speter				GETC_(frew, c)
91811894Speter			    else
91911894Speter				cachegeteof_(c, goto keystring_eof;)
92011894Speter			    if (tp <= &keyval.string[keylength])
9219Sjkh				switch (ctab[c]) {
9229Sjkh				    case LETTER: case Letter:
9239Sjkh					*tp++ = c;
9249Sjkh					continue;
9259Sjkh				    default:
9269Sjkh					break;
9279Sjkh				}
9289Sjkh			    break;
9299Sjkh                        }
9309Sjkh			*tp++ = c; *tp = '\0';
9319Sjkh			matchresult = trymatch(keyval.string+1);
9329Sjkh			if (matchresult==Nomatch) {
9339Sjkh				tp[-1] = 0;
9349Sjkh				aputs(keyval.string, out);
9359Sjkh				continue;   /* last c handled properly */
9369Sjkh			}
9379Sjkh
9389Sjkh			/* Now we have a keyword terminated with a K/VDELIM */
9399Sjkh			if (c==VDELIM) {
9409Sjkh			      /* try to find closing KDELIM, and replace value */
9419Sjkh			      tlim = keyval.string + keyval.size;
9429Sjkh			      for (;;) {
94311894Speter				      if (ds)
94411894Speter					GETC_(frew, c)
94511894Speter				      else
94611894Speter					cachegeteof_(c, goto keystring_eof;)
9479Sjkh				      if (c=='\n' || c==KDELIM)
9489Sjkh					break;
9499Sjkh				      *tp++ =c;
9509Sjkh				      if (tlim <= tp)
9519Sjkh					  tp = bufenlarge(&keyval, &tlim);
9529Sjkh				      if (c==SDELIM && ds) { /*skip next SDELIM */
95311894Speter						GETC_(frew, c)
9549Sjkh						if (c != SDELIM) {
9559Sjkh							/* end of string before closing KDELIM or newline */
9569Sjkh							nextc = c;
9579Sjkh							goto keystring_eof;
9589Sjkh						}
9599Sjkh				      }
9609Sjkh			      }
9619Sjkh			      if (c!=KDELIM) {
9629Sjkh				    /* couldn't find closing KDELIM -- give up */
9639Sjkh				    *tp = 0;
9649Sjkh				    aputs(keyval.string, out);
9659Sjkh				    continue;   /* last c handled properly */
9669Sjkh			      }
9679Sjkh			}
9689Sjkh			/* now put out the new keyword value */
96911894Speter			uncache(infile);
97011894Speter			keyreplace(matchresult, delta, ds, infile, out, dolog);
97111894Speter			cache(infile);
9729Sjkh			e = 1;
9739Sjkh			break;
9749Sjkh                }
9759Sjkh		break;
9769Sjkh	    }
9779Sjkh        }
9789Sjkh
9799Sjkh    keystring_eof:
9809Sjkh	*tp = 0;
9819Sjkh	aputs(keyval.string, out);
9829Sjkh    uncache_exit:
9839Sjkh	uncache(infile);
9849Sjkh	return r + e;
9859Sjkh}
9869Sjkh
9879Sjkh
98811894Speter	static void
98911894Speterescape_string(out, s)
99011894Speter	register FILE *out;
99111894Speter	register char const *s;
99211894Speter/* Output to OUT the string S, escaping chars that would break `ci -k'.  */
99311894Speter{
99411894Speter    register char c;
99511894Speter    for (;;)
99611894Speter	switch ((c = *s++)) {
99711894Speter	    case 0: return;
99811894Speter	    case '\t': aputs("\\t", out); break;
99911894Speter	    case '\n': aputs("\\n", out); break;
100011894Speter	    case ' ': aputs("\\040", out); break;
100111894Speter	    case KDELIM: aputs("\\044", out); break;
100211894Speter	    case '\\': if (VERSION(5)<=RCSversion) {aputs("\\\\", out); break;}
100311894Speter	    /* fall into */
100411894Speter	    default: aputc_(c, out) break;
100511894Speter	}
100611894Speter}
100711894Speter
10089Sjkhchar const ciklog[ciklogsize] = "checked in with -k by ";
10099Sjkh
10109Sjkh	static void
101111894Speterkeyreplace(marker, delta, delimstuffed, infile, out, dolog)
10129Sjkh	enum markers marker;
10139Sjkh	register struct hshentry const *delta;
101411894Speter	int delimstuffed;
101511894Speter	RILE *infile;
10169Sjkh	register FILE *out;
101711894Speter	int dolog;
10189Sjkh/* function: outputs the keyword value(s) corresponding to marker.
10199Sjkh * Attributes are derived from delta.
10209Sjkh */
10219Sjkh{
10229Sjkh	register char const *sp, *cp, *date;
102311894Speter	register int c;
10249Sjkh	register size_t cs, cw, ls;
10259Sjkh	char const *sp1;
102611894Speter	char datebuf[datesize + zonelenmax];
10279Sjkh	int RCSv;
102811894Speter	int exp;
10299Sjkh
10309Sjkh	sp = Keyword[(int)marker];
103111894Speter	exp = Expand;
103211894Speter	date = delta->date;
103311894Speter	RCSv = RCSversion;
10349Sjkh
103511894Speter	if (exp != VAL_EXPAND)
103611894Speter	    aprintf(out, "%c%s", KDELIM, sp);
103711894Speter	if (exp != KEY_EXPAND) {
10389Sjkh
103911894Speter	    if (exp != VAL_EXPAND)
104011894Speter		aprintf(out, "%c%c", VDELIM,
10419Sjkh			marker==Log && RCSv<VERSION(5)  ?  '\t'  :  ' '
10429Sjkh		);
10439Sjkh
104411894Speter	    switch (marker) {
104511894Speter	    case Author:
10469Sjkh		aputs(delta->author, out);
10479Sjkh                break;
104811894Speter	    case Date:
10499Sjkh		aputs(date2str(date,datebuf), out);
10509Sjkh                break;
105111894Speter	    case Id:
105225699Speter	    case LocalId:
105311894Speter	    case Header:
105455808Speter	    case CVSHeader:
105525699Speter		if (marker == Id || RCSv < VERSION(4) ||
105625699Speter		    (marker == LocalId && LocalIdMode == Id))
105725699Speter			escape_string(out, basefilename(RCSname));
105825699Speter		else if (marker == CVSHeader ||
105925699Speter		    (marker == LocalId && LocalIdMode == CVSHeader))
106025699Speter			escape_string(out, getfullCVSname());
106125699Speter		else
106225699Speter			escape_string(out, getfullRCSname());
106311894Speter		aprintf(out, " %s %s %s %s",
10649Sjkh			delta->num,
10659Sjkh			date2str(date, datebuf),
10669Sjkh			delta->author,
10679Sjkh			  RCSv==VERSION(3) && delta->lockedby ? "Locked"
10689Sjkh			: delta->state
10699Sjkh		);
107011894Speter		if (delta->lockedby)
10719Sjkh		    if (VERSION(5) <= RCSv) {
107211894Speter			if (locker_expansion || exp==KEYVALLOCK_EXPAND)
10739Sjkh			    aprintf(out, " %s", delta->lockedby);
10749Sjkh		    } else if (RCSv == VERSION(4))
10759Sjkh			aprintf(out, " Locker: %s", delta->lockedby);
10769Sjkh                break;
107711894Speter	    case Locker:
10789Sjkh		if (delta->lockedby)
10799Sjkh		    if (
10809Sjkh				locker_expansion
108111894Speter			||	exp == KEYVALLOCK_EXPAND
10829Sjkh			||	RCSv <= VERSION(4)
10839Sjkh		    )
10849Sjkh			aputs(delta->lockedby, out);
10859Sjkh                break;
108611894Speter	    case Log:
108711894Speter	    case RCSfile:
108811894Speter		escape_string(out, basefilename(RCSname));
10899Sjkh                break;
109011894Speter	    case Name:
109111894Speter		if (delta->name)
109211894Speter			aputs(delta->name, out);
109311894Speter		break;
109411894Speter	    case Revision:
10959Sjkh		aputs(delta->num, out);
10969Sjkh                break;
109711894Speter	    case Source:
109811894Speter		escape_string(out, getfullRCSname());
10999Sjkh                break;
110011894Speter	    case State:
11019Sjkh		aputs(delta->state, out);
11029Sjkh                break;
110311894Speter	    default:
11049Sjkh		break;
110511894Speter	    }
110611894Speter	    if (exp != VAL_EXPAND)
11079Sjkh		afputc(' ', out);
11089Sjkh	}
110911894Speter	if (exp != VAL_EXPAND)
111011894Speter	    afputc(KDELIM, out);
111111894Speter
111211894Speter	if (marker == Log   &&  dolog) {
111311894Speter		struct buf leader;
111411894Speter
11159Sjkh		sp = delta->log.string;
11169Sjkh		ls = delta->log.size;
11179Sjkh		if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
11189Sjkh			return;
111911894Speter		bufautobegin(&leader);
112011894Speter		if (RCSversion < VERSION(5)) {
112111894Speter		    cp = Comment.string;
112211894Speter		    cs = Comment.size;
112311894Speter		} else {
112411894Speter		    int kdelim_found = 0;
112511894Speter		    Ioffset_type chars_read = Itell(infile);
112611894Speter		    declarecache;
112711894Speter		    setupcache(infile); cache(infile);
112811894Speter
112911894Speter		    c = 0; /* Pacify `gcc -Wall'.  */
113011894Speter
113111894Speter		    /*
113211894Speter		    * Back up to the start of the current input line,
113311894Speter		    * setting CS to the number of characters before `$Log'.
113411894Speter		    */
113511894Speter		    cs = 0;
113611894Speter		    for (;;) {
113711894Speter			if (!--chars_read)
113811894Speter			    goto done_backing_up;
113911894Speter			cacheunget_(infile, c)
114011894Speter			if (c == '\n')
114111894Speter			    break;
114211894Speter			if (c == SDELIM  &&  delimstuffed) {
114311894Speter			    if (!--chars_read)
114411894Speter				break;
114511894Speter			    cacheunget_(infile, c)
114611894Speter			    if (c != SDELIM) {
114711894Speter				cacheget_(c)
114811894Speter				break;
114911894Speter			    }
115011894Speter			}
115111894Speter			cs += kdelim_found;
115211894Speter			kdelim_found |= c==KDELIM;
115311894Speter		    }
115411894Speter		    cacheget_(c)
115511894Speter		  done_backing_up:;
115611894Speter
115711894Speter		    /* Copy characters before `$Log' into LEADER.  */
115811894Speter		    bufalloc(&leader, cs);
115911894Speter		    cp = leader.string;
116011894Speter		    for (cw = 0;  cw < cs;  cw++) {
116111894Speter			leader.string[cw] = c;
116211894Speter			if (c == SDELIM  &&  delimstuffed)
116311894Speter			    cacheget_(c)
116411894Speter			cacheget_(c)
116511894Speter		    }
116611894Speter
116711894Speter		    /* Convert traditional C or Pascal leader to ` *'.  */
116811894Speter		    for (cw = 0;  cw < cs;  cw++)
116911894Speter			if (ctab[(unsigned char) cp[cw]] != SPACE)
117011894Speter			    break;
117111894Speter		    if (
117211894Speter			cw+1 < cs
117311894Speter			&&  cp[cw+1] == '*'
117411894Speter			&&  (cp[cw] == '/'  ||  cp[cw] == '(')
117511894Speter		    ) {
117611894Speter			size_t i = cw+1;
117711894Speter			for (;;)
117811894Speter			    if (++i == cs) {
117911894Speter				warn(
118011894Speter				    "`%c* $Log' is obsolescent; use ` * $Log'.",
118111894Speter				    cp[cw]
118211894Speter				);
118311894Speter				leader.string[cw] = ' ';
118411894Speter				break;
118511894Speter			    } else if (ctab[(unsigned char) cp[i]] != SPACE)
118611894Speter				break;
118711894Speter		    }
118811894Speter
118911894Speter		    /* Skip `$Log ... $' string.  */
119011894Speter		    do {
119111894Speter			cacheget_(c)
119211894Speter		    } while (c != KDELIM);
119311894Speter		    uncache(infile);
119411894Speter		}
11959Sjkh		afputc('\n', out);
11969Sjkh		awrite(cp, cs, out);
119711894Speter		sp1 = date2str(date, datebuf);
119811894Speter		if (VERSION(5) <= RCSv) {
119911894Speter		    aprintf(out, "Revision %s  %s  %s",
120011894Speter			delta->num, sp1, delta->author
120111894Speter		    );
120211894Speter		} else {
120311894Speter		    /* oddity: 2 spaces between date and time, not 1 as usual */
120411894Speter		    sp1 = strchr(sp1, ' ');
120511894Speter		    aprintf(out, "Revision %s  %.*s %s  %s",
120611894Speter			delta->num, (int)(sp1-datebuf), datebuf, sp1,
120711894Speter			delta->author
120811894Speter		    );
120911894Speter		}
12109Sjkh		/* Do not include state: it may change and is not updated.  */
121111894Speter		cw = cs;
12129Sjkh		if (VERSION(5) <= RCSv)
12139Sjkh		    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
121411894Speter			continue;
12159Sjkh		for (;;) {
12169Sjkh		    afputc('\n', out);
12179Sjkh		    awrite(cp, cw, out);
12189Sjkh		    if (!ls)
12199Sjkh			break;
12209Sjkh		    --ls;
12219Sjkh		    c = *sp++;
12229Sjkh		    if (c != '\n') {
12239Sjkh			awrite(cp+cw, cs-cw, out);
12249Sjkh			do {
12259Sjkh			    afputc(c,out);
12269Sjkh			    if (!ls)
12279Sjkh				break;
12289Sjkh			    --ls;
12299Sjkh			    c = *sp++;
12309Sjkh			} while (c != '\n');
12319Sjkh		    }
12329Sjkh		}
123311894Speter		bufautoend(&leader);
12349Sjkh	}
12359Sjkh}
12369Sjkh
12379Sjkh#if has_readlink
123811894Speter	static int resolve_symlink P((struct buf*));
12399Sjkh	static int
12409Sjkhresolve_symlink(L)
12419Sjkh	struct buf *L;
12429Sjkh/*
12439Sjkh * If L is a symbolic link, resolve it to the name that it points to.
12449Sjkh * If unsuccessful, set errno and yield -1.
12459Sjkh * If it points to an existing file, yield 1.
12469Sjkh * Otherwise, set errno=ENOENT and yield 0.
12479Sjkh */
12489Sjkh{
12499Sjkh	char *b, a[SIZEABLE_PATH];
12509Sjkh	int e;
12519Sjkh	size_t s;
12529Sjkh	ssize_t r;
12539Sjkh	struct buf bigbuf;
125411894Speter	int linkcount = MAXSYMLINKS;
12559Sjkh
12569Sjkh	b = a;
12579Sjkh	s = sizeof(a);
12589Sjkh	bufautobegin(&bigbuf);
12599Sjkh	while ((r = readlink(L->string,b,s))  !=  -1)
12609Sjkh	    if (r == s) {
12619Sjkh		bufalloc(&bigbuf, s<<1);
12629Sjkh		b = bigbuf.string;
12639Sjkh		s = bigbuf.size;
126411894Speter	    } else if (!linkcount--) {
126511894Speter#		ifndef ELOOP
126611894Speter		    /*
126711894Speter		    * Some pedantic Posix 1003.1-1990 hosts have readlink
126811894Speter		    * but not ELOOP.  Approximate ELOOP with EMLINK.
126911894Speter		    */
127011894Speter#		    define ELOOP EMLINK
127111894Speter#		endif
12729Sjkh		errno = ELOOP;
12739Sjkh		return -1;
12749Sjkh	    } else {
12759Sjkh		/* Splice symbolic link into L.  */
12769Sjkh		b[r] = '\0';
127711894Speter		L->string[
127811894Speter		  ROOTPATH(b)  ?  0  :  basefilename(L->string) - L->string
127911894Speter		] = '\0';
12809Sjkh		bufscat(L, b);
12819Sjkh	    }
12829Sjkh	e = errno;
12839Sjkh	bufautoend(&bigbuf);
12849Sjkh	errno = e;
12859Sjkh	switch (e) {
128611894Speter	    case readlink_isreg_errno: return 1;
12879Sjkh	    case ENOENT: return 0;
12889Sjkh	    default: return -1;
12899Sjkh	}
12909Sjkh}
12919Sjkh#endif
12929Sjkh
12939Sjkh	RILE *
12949Sjkhrcswriteopen(RCSbuf, status, mustread)
12959Sjkh	struct buf *RCSbuf;
12969Sjkh	struct stat *status;
12979Sjkh	int mustread;
12989Sjkh/*
129911894Speter * Create the lock file corresponding to RCSBUF.
130011894Speter * Then try to open RCSBUF for reading and yield its RILE* descriptor.
13019Sjkh * Put its status into *STATUS too.
13029Sjkh * MUSTREAD is true if the file must already exist, too.
13039Sjkh * If all goes well, discard any previously acquired locks,
130411894Speter * and set fdlock to the file descriptor of the RCS lockfile.
13059Sjkh */
13069Sjkh{
13079Sjkh	register char *tp;
130811894Speter	register char const *sp, *RCSpath, *x;
13099Sjkh	RILE *f;
13109Sjkh	size_t l;
131111894Speter	int e, exists, fdesc, fdescSafer, r, waslocked;
13129Sjkh	struct buf *dirt;
13139Sjkh	struct stat statbuf;
13149Sjkh
131511894Speter	waslocked  =  0 <= fdlock;
13169Sjkh	exists =
13179Sjkh#		if has_readlink
13189Sjkh			resolve_symlink(RCSbuf);
13199Sjkh#		else
13209Sjkh			    stat(RCSbuf->string, &statbuf) == 0  ?  1
13219Sjkh			:   errno==ENOENT ? 0 : -1;
13229Sjkh#		endif
132311894Speter	if (exists < (mustread|waslocked))
13249Sjkh		/*
13259Sjkh		 * There's an unusual problem with the RCS file;
13269Sjkh		 * or the RCS file doesn't exist,
13279Sjkh		 * and we must read or we already have a lock elsewhere.
13289Sjkh		 */
13299Sjkh		return 0;
13309Sjkh
133111894Speter	RCSpath = RCSbuf->string;
133211894Speter	sp = basefilename(RCSpath);
133311894Speter	l = sp - RCSpath;
133411894Speter	dirt = &dirtpname[waslocked];
133511894Speter	bufscpy(dirt, RCSpath);
13369Sjkh	tp = dirt->string + l;
133711894Speter	x = rcssuffix(RCSpath);
13389Sjkh#	if has_readlink
13399Sjkh	    if (!x) {
134011894Speter		error("symbolic link to non RCS file `%s'", RCSpath);
13419Sjkh		errno = EINVAL;
13429Sjkh		return 0;
13439Sjkh	    }
13449Sjkh#	endif
13459Sjkh	if (*sp == *x) {
134611894Speter		error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
13479Sjkh		errno = EINVAL;
13489Sjkh		return 0;
13499Sjkh	}
135011894Speter	/* Create a lock filename that is a function of the RCS filename.  */
13519Sjkh	if (*x) {
13529Sjkh		/*
13539Sjkh		 * The suffix is nonempty.
13549Sjkh		 * The lock filename is the first char of of the suffix,
13559Sjkh		 * followed by the RCS filename with last char removed.  E.g.:
13569Sjkh		 *	foo,v	RCS filename with suffix ,v
13579Sjkh		 *	,foo,	lock filename
13589Sjkh		 */
13599Sjkh		*tp++ = *x;
13609Sjkh		while (*sp)
13619Sjkh			*tp++ = *sp++;
13629Sjkh		*--tp = 0;
13639Sjkh	} else {
13649Sjkh		/*
13659Sjkh		 * The suffix is empty.
13669Sjkh		 * The lock filename is the RCS filename
13679Sjkh		 * with last char replaced by '_'.
13689Sjkh		 */
13699Sjkh		while ((*tp++ = *sp++))
137011894Speter			continue;
13719Sjkh		tp -= 2;
13729Sjkh		if (*tp == '_') {
137311894Speter			error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
13749Sjkh			errno = EINVAL;
13759Sjkh			return 0;
13769Sjkh		}
13779Sjkh		*tp = '_';
13789Sjkh	}
13799Sjkh
138011894Speter	sp = dirt->string;
13819Sjkh
13829Sjkh	f = 0;
13839Sjkh
13849Sjkh	/*
13859Sjkh	* good news:
138611894Speter	*	open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
138711894Speter	*	is atomic according to Posix 1003.1-1990.
13889Sjkh	* bad news:
13899Sjkh	*	NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
13909Sjkh	* good news:
139111894Speter	*	(O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity
139211894Speter	*	even with NFS.
13939Sjkh	* bad news:
139411894Speter	*	If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
139511894Speter	*	guarantee atomicity.
13969Sjkh	* good news:
13979Sjkh	*	Root-over-the-wire NFS access is rare for security reasons.
13989Sjkh	*	This bug has never been reported in practice with RCS.
13999Sjkh	* So we don't worry about this bug.
14009Sjkh	*
14019Sjkh	* An even rarer NFS bug can occur when clients retry requests.
140211894Speter	* This can happen in the usual case of NFS over UDP.
140311894Speter	* Suppose client A releases a lock by renaming ",f," to "f,v" at
140411894Speter	* about the same time that client B obtains a lock by creating ",f,",
14059Sjkh	* and suppose A's first rename request is delayed, so A reissues it.
14069Sjkh	* The sequence of events might be:
14079Sjkh	*	A sends rename(",f,", "f,v")
14089Sjkh	*	B sends create(",f,")
14099Sjkh	*	A sends retry of rename(",f,", "f,v")
14109Sjkh	*	server receives, does, and acknowledges A's first rename()
14119Sjkh	*	A receives acknowledgment, and its RCS program exits
14129Sjkh	*	server receives, does, and acknowledges B's create()
14139Sjkh	*	server receives, does, and acknowledges A's retry of rename()
14149Sjkh	* This not only wrongly deletes B's lock, it removes the RCS file!
14159Sjkh	* Most NFS implementations have idempotency caches that usually prevent
14169Sjkh	* this scenario, but such caches are finite and can be overrun.
141711894Speter	* This problem afflicts not only RCS, which uses open() and rename()
141811894Speter	* to get and release locks; it also afflicts the traditional
14199Sjkh	* Unix method of using link() and unlink() to get and release locks,
142011894Speter	* and the less traditional method of using mkdir() and rmdir().
142111894Speter	* There is no easy workaround.
14229Sjkh	* Any new method based on lockf() seemingly would be incompatible with
14239Sjkh	* the old methods; besides, lockf() is notoriously buggy under NFS.
14249Sjkh	* Since this problem afflicts scads of Unix programs, but is so rare
14259Sjkh	* that nobody seems to be worried about it, we won't worry either.
14269Sjkh	*/
14279Sjkh#	if !open_can_creat
142811894Speter#		define create(f) creat(f, OPEN_CREAT_READONLY)
14299Sjkh#	else
143011894Speter#		define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
14319Sjkh#	endif
14329Sjkh
14339Sjkh	catchints();
14349Sjkh	ignoreints();
14359Sjkh
14369Sjkh	/*
14379Sjkh	 * Create a lock file for an RCS file.  This should be atomic, i.e.
14389Sjkh	 * if two processes try it simultaneously, at most one should succeed.
14399Sjkh	 */
14409Sjkh	seteid();
14419Sjkh	fdesc = create(sp);
144211894Speter	fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr.  */
14439Sjkh	e = errno;
14449Sjkh	setrid();
14459Sjkh
144611894Speter	if (0 <= fdesc)
144711894Speter		dirtpmaker[0] = effective;
144811894Speter
144911894Speter	if (fdescSafer < 0) {
145011894Speter		if (e == EACCES  &&  stat(sp,&statbuf) == 0)
14519Sjkh			/* The RCS file is busy.  */
14529Sjkh			e = EEXIST;
14539Sjkh	} else {
14549Sjkh		e = ENOENT;
14559Sjkh		if (exists) {
145611894Speter		    f = Iopen(RCSpath, FOPEN_RB, status);
14579Sjkh		    e = errno;
145811894Speter		    if (f && waslocked) {
14599Sjkh			/* Discard the previous lock in favor of this one.  */
146011894Speter			ORCSclose();
14619Sjkh			seteid();
146211894Speter			r = un_link(lockname);
146311894Speter			e = errno;
14649Sjkh			setrid();
14659Sjkh			if (r != 0)
146611894Speter			    enfaterror(e, lockname);
146711894Speter			bufscpy(&dirtpname[lockdirtp_index], sp);
14689Sjkh		    }
14699Sjkh		}
147011894Speter		fdlock = fdescSafer;
14719Sjkh	}
14729Sjkh
14739Sjkh	restoreints();
14749Sjkh
14759Sjkh	errno = e;
14769Sjkh	return f;
14779Sjkh}
14789Sjkh
14799Sjkh	void
14809Sjkhkeepdirtemp(name)
14819Sjkh	char const *name;
14829Sjkh/* Do not unlink name, either because it's not there any more,
14839Sjkh * or because it has already been unlinked.
14849Sjkh */
14859Sjkh{
14869Sjkh	register int i;
14879Sjkh	for (i=DIRTEMPNAMES; 0<=--i; )
148811894Speter		if (dirtpname[i].string == name) {
148911894Speter			dirtpmaker[i] = notmade;
14909Sjkh			return;
14919Sjkh		}
14929Sjkh	faterror("keepdirtemp");
14939Sjkh}
14949Sjkh
14959Sjkh	char const *
149611894Spetermakedirtemp(isworkfile)
149711894Speter	int isworkfile;
14989Sjkh/*
149911894Speter * Create a unique pathname and store it into dirtpname.
150011894Speter * Because of storage in tpnames, dirtempunlink() can unlink the file later.
150111894Speter * Return a pointer to the pathname created.
150211894Speter * If ISWORKFILE is 1, put it into the working file's directory;
150311894Speter * if 0, put the unique file in RCSfile's directory.
15049Sjkh */
15059Sjkh{
15069Sjkh	register char *tp, *np;
15079Sjkh	register size_t dl;
15089Sjkh	register struct buf *bn;
150911894Speter	register char const *name = isworkfile ? workname : RCSname;
151076301Skris#	if has_mktemp
151176301Skris	int fd;
151276301Skris#	endif
15139Sjkh
151411894Speter	dl = basefilename(name) - name;
151511894Speter	bn = &dirtpname[newRCSdirtp_index + isworkfile];
15169Sjkh	bufalloc(bn,
15179Sjkh#		if has_mktemp
15189Sjkh			dl + 9
15199Sjkh#		else
15209Sjkh			strlen(name) + 3
15219Sjkh#		endif
15229Sjkh	);
15239Sjkh	bufscpy(bn, name);
15249Sjkh	np = tp = bn->string;
15259Sjkh	tp += dl;
15269Sjkh	*tp++ = '_';
152711894Speter	*tp++ = '0'+isworkfile;
15289Sjkh	catchints();
15299Sjkh#	if has_mktemp
15309Sjkh		VOID strcpy(tp, "XXXXXX");
153176301Skris		fd = mkstemp(np);
153276301Skris		if (fd < 0 || !*np)
153311894Speter		    faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
153411894Speter			(int)dl, name, '0'+isworkfile
15359Sjkh		    );
153676301Skris		close(fd);
15379Sjkh#	else
15389Sjkh		/*
15399Sjkh		 * Posix 1003.1-1990 has no reliable way
15409Sjkh		 * to create a unique file in a named directory.
154111894Speter		 * We fudge here.  If the filename is abcde,
15429Sjkh		 * the temp filename is _Ncde where N is a digit.
15439Sjkh		 */
15449Sjkh		name += dl;
15459Sjkh		if (*name) name++;
15469Sjkh		if (*name) name++;
15479Sjkh		VOID strcpy(tp, name);
15489Sjkh#	endif
154911894Speter	dirtpmaker[newRCSdirtp_index + isworkfile] = real;
15509Sjkh	return np;
15519Sjkh}
15529Sjkh
15539Sjkh	void
15549Sjkhdirtempunlink()
15559Sjkh/* Clean up makedirtemp() files.  May be invoked by signal handler. */
15569Sjkh{
15579Sjkh	register int i;
15589Sjkh	enum maker m;
15599Sjkh
15609Sjkh	for (i = DIRTEMPNAMES;  0 <= --i;  )
156111894Speter	    if ((m = dirtpmaker[i]) != notmade) {
15629Sjkh		if (m == effective)
15639Sjkh		    seteid();
156411894Speter		VOID un_link(dirtpname[i].string);
15659Sjkh		if (m == effective)
15669Sjkh		    setrid();
156711894Speter		dirtpmaker[i] = notmade;
15689Sjkh	    }
15699Sjkh}
15709Sjkh
15719Sjkh
15729Sjkh	int
15739Sjkh#if has_prototypes
157411894Speterchnamemod(
157511894Speter	FILE **fromp, char const *from, char const *to,
157611894Speter	int set_mode, mode_t mode, time_t mtime
157711894Speter)
15789Sjkh  /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
15799Sjkh#else
158011894Speter  chnamemod(fromp, from, to, set_mode, mode, mtime)
158111894Speter	FILE **fromp; char const *from,*to;
158211894Speter	int set_mode; mode_t mode; time_t mtime;
15839Sjkh#endif
15849Sjkh/*
158511894Speter * Rename a file (with stream pointer *FROMP) from FROM to TO.
15869Sjkh * FROM already exists.
158711894Speter * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
158811894Speter * If MTIME is not -1, change its mtime to MTIME before renaming.
158911894Speter * Close and clear *FROMP before renaming it.
15909Sjkh * Unlink TO if it already exists.
15919Sjkh * Return -1 on error (setting errno), 0 otherwise.
15929Sjkh */
15939Sjkh{
159411894Speter	mode_t mode_while_renaming = mode;
159511894Speter	int fchmod_set_mode = 0;
159611894Speter
159711894Speter#	if bad_a_rename || bad_NFS_rename
159811894Speter	    struct stat st;
159911894Speter	    if (bad_NFS_rename  ||  (bad_a_rename && set_mode <= 0)) {
160011894Speter		if (fstat(fileno(*fromp), &st) != 0)
160111894Speter		    return -1;
160211894Speter		if (bad_a_rename && set_mode <= 0)
160311894Speter		    mode = st.st_mode;
160411894Speter	    }
160511894Speter#	endif
160611894Speter
16079Sjkh#	if bad_a_rename
16089Sjkh		/*
160911894Speter		* There's a short window of inconsistency
161011894Speter		* during which the lock file is writable.
161111894Speter		*/
161211894Speter		mode_while_renaming = mode|S_IWUSR;
161311894Speter		if (mode != mode_while_renaming)
161411894Speter		    set_mode = 1;
16159Sjkh#	endif
161611894Speter
16179Sjkh#	if has_fchmod
161811894Speter	    if (0<set_mode  &&  fchmod(fileno(*fromp),mode_while_renaming) == 0)
161911894Speter		fchmod_set_mode = set_mode;
16209Sjkh#	endif
162111894Speter	/* If bad_chmod_close, we must close before chmod.  */
162211894Speter	Ozclose(fromp);
162311894Speter	if (fchmod_set_mode<set_mode  &&  chmod(from, mode_while_renaming) != 0)
162411894Speter	    return -1;
162511894Speter
162611894Speter	if (setmtime(from, mtime) != 0)
16279Sjkh		return -1;
16289Sjkh
16299Sjkh#	if !has_rename || bad_b_rename
16309Sjkh		/*
163111894Speter		* There's a short window of inconsistency
163211894Speter		* during which TO does not exist.
163311894Speter		*/
163411894Speter		if (un_link(to) != 0  &&  errno != ENOENT)
163511894Speter			return -1;
16369Sjkh#	endif
16379Sjkh
163811894Speter#	if has_rename
163911894Speter	    if (rename(from,to) != 0  &&  !(has_NFS && errno==ENOENT))
164011894Speter		return -1;
164111894Speter#	else
164211894Speter	    if (do_link(from,to) != 0  ||  un_link(from) != 0)
164311894Speter		return -1;
164411894Speter#	endif
16459Sjkh
164611894Speter#	if bad_NFS_rename
164711894Speter	{
164811894Speter	    /*
164911894Speter	    * Check whether the rename falsely reported success.
165011894Speter	    * A race condition can occur between the rename and the stat.
165111894Speter	    */
165211894Speter	    struct stat tostat;
165311894Speter	    if (stat(to, &tostat) != 0)
165411894Speter		return -1;
165511894Speter	    if (! same_file(st, tostat, 0)) {
165611894Speter		errno = EIO;
165711894Speter		return -1;
165811894Speter	    }
165911894Speter	}
166011894Speter#	endif
166111894Speter
166211894Speter#	if bad_a_rename
166311894Speter	    if (0 < set_mode  &&  chmod(to, mode) != 0)
166411894Speter		return -1;
166511894Speter#	endif
166611894Speter
166711894Speter	return 0;
16689Sjkh}
16699Sjkh
167011894Speter	int
167111894Spetersetmtime(file, mtime)
167211894Speter	char const *file;
167311894Speter	time_t mtime;
167411894Speter/* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1.  */
167511894Speter{
167611894Speter	static struct utimbuf amtime; /* static so unused fields are zero */
167711894Speter	if (mtime == -1)
167811894Speter		return 0;
167911894Speter	amtime.actime = now();
168011894Speter	amtime.modtime = mtime;
168111894Speter	return utime(file, &amtime);
168211894Speter}
16839Sjkh
16849Sjkh
168511894Speter
16869Sjkh	int
16879Sjkhfindlock(delete, target)
16889Sjkh	int delete;
16899Sjkh	struct hshentry **target;
16909Sjkh/*
16919Sjkh * Find the first lock held by caller and return a pointer
16929Sjkh * to the locked delta; also removes the lock if DELETE.
16939Sjkh * If one lock, put it into *TARGET.
16949Sjkh * Return 0 for no locks, 1 for one, 2 for two or more.
16959Sjkh */
16969Sjkh{
169711894Speter	register struct rcslock *next, **trail, **found;
16989Sjkh
16999Sjkh	found = 0;
17009Sjkh	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
17019Sjkh		if (strcmp(getcaller(), next->login)  ==  0) {
17029Sjkh			if (found) {
170311894Speter				rcserror("multiple revisions locked by %s; please specify one", getcaller());
17049Sjkh				return 2;
17059Sjkh			}
17069Sjkh			found = trail;
17079Sjkh		}
17089Sjkh	if (!found)
17099Sjkh		return 0;
17109Sjkh	next = *found;
17119Sjkh	*target = next->delta;
17129Sjkh	if (delete) {
171311894Speter		next->delta->lockedby = 0;
17149Sjkh		*found = next->nextlock;
17159Sjkh	}
17169Sjkh	return 1;
17179Sjkh}
17189Sjkh
17199Sjkh	int
172011894Speteraddlock(delta, verbose)
17219Sjkh	struct hshentry * delta;
172211894Speter	int verbose;
17239Sjkh/*
17249Sjkh * Add a lock held by caller to DELTA and yield 1 if successful.
172511894Speter * Print an error message if verbose and yield -1 if no lock is added because
17269Sjkh * DELTA is locked by somebody other than caller.
17279Sjkh * Return 0 if the caller already holds the lock.
17289Sjkh */
17299Sjkh{
173011894Speter	register struct rcslock *next;
17319Sjkh
17329Sjkh	for (next = Locks;  next;  next = next->nextlock)
17339Sjkh		if (cmpnum(delta->num, next->delta->num) == 0)
17349Sjkh			if (strcmp(getcaller(), next->login) == 0)
17359Sjkh				return 0;
17369Sjkh			else {
173711894Speter				if (verbose)
173811894Speter				  rcserror("Revision %s is already locked by %s.",
173911894Speter					delta->num, next->login
174011894Speter				  );
17419Sjkh				return -1;
17429Sjkh			}
174311894Speter	next = ftalloc(struct rcslock);
17449Sjkh	delta->lockedby = next->login = getcaller();
17459Sjkh	next->delta = delta;
17469Sjkh	next->nextlock = Locks;
17479Sjkh	Locks = next;
17489Sjkh	return 1;
17499Sjkh}
17509Sjkh
17519Sjkh
17529Sjkh	int
17539Sjkhaddsymbol(num, name, rebind)
17549Sjkh	char const *num, *name;
17559Sjkh	int rebind;
17569Sjkh/*
17579Sjkh * Associate with revision NUM the new symbolic NAME.
17589Sjkh * If NAME already exists and REBIND is set, associate NAME with NUM;
17599Sjkh * otherwise, print an error message and return false;
176011894Speter * Return -1 if unsuccessful, 0 if no change, 1 if change.
17619Sjkh */
17629Sjkh{
17639Sjkh	register struct assoc *next;
17649Sjkh
17659Sjkh	for (next = Symbols;  next;  next = next->nextassoc)
17669Sjkh		if (strcmp(name, next->symbol)  ==  0)
176711894Speter			if (strcmp(next->num,num) == 0)
176811894Speter				return 0;
176911894Speter			else if (rebind) {
17709Sjkh				next->num = num;
177111894Speter				return 1;
17729Sjkh			} else {
177311894Speter				rcserror("symbolic name %s already bound to %s",
17749Sjkh					name, next->num
17759Sjkh				);
177611894Speter				return -1;
17779Sjkh			}
17789Sjkh	next = ftalloc(struct assoc);
17799Sjkh	next->symbol = name;
17809Sjkh	next->num = num;
17819Sjkh	next->nextassoc = Symbols;
17829Sjkh	Symbols = next;
178311894Speter	return 1;
17849Sjkh}
17859Sjkh
17869Sjkh
17879Sjkh
17889Sjkh	char const *
17899Sjkhgetcaller()
17909Sjkh/* Get the caller's login name.  */
17919Sjkh{
17929Sjkh#	if has_setuid
17939Sjkh		return getusername(euid()!=ruid());
17949Sjkh#	else
17959Sjkh		return getusername(false);
17969Sjkh#	endif
17979Sjkh}
17989Sjkh
17999Sjkh
18009Sjkh	int
18019Sjkhcheckaccesslist()
18029Sjkh/*
18039Sjkh * Return true if caller is the superuser, the owner of the
18049Sjkh * file, the access list is empty, or caller is on the access list.
18059Sjkh * Otherwise, print an error message and return false.
18069Sjkh */
18079Sjkh{
18089Sjkh	register struct access const *next;
18099Sjkh
18109Sjkh	if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
18119Sjkh		return true;
18129Sjkh
18139Sjkh	next = AccessList;
18149Sjkh	do {
18159Sjkh		if (strcmp(getcaller(), next->login)  ==  0)
18169Sjkh			return true;
18179Sjkh	} while ((next = next->nextaccess));
18189Sjkh
181911894Speter	rcserror("user %s not on the access list", getcaller());
18209Sjkh	return false;
18219Sjkh}
18229Sjkh
18239Sjkh
18249Sjkh	int
18259Sjkhdorewrite(lockflag, changed)
18269Sjkh	int lockflag, changed;
18279Sjkh/*
18289Sjkh * Do nothing if LOCKFLAG is zero.
18299Sjkh * Prepare to rewrite an RCS file if CHANGED is positive.
18309Sjkh * Stop rewriting if CHANGED is zero, because there won't be any changes.
18319Sjkh * Fail if CHANGED is negative.
183211894Speter * Return 0 on success, -1 on failure.
18339Sjkh */
18349Sjkh{
183511894Speter	int r = 0, e;
18369Sjkh
18379Sjkh	if (lockflag)
18389Sjkh		if (changed) {
18399Sjkh			if (changed < 0)
184011894Speter				return -1;
184111894Speter			putadmin();
18429Sjkh			puttree(Head, frewrite);
18439Sjkh			aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
18449Sjkh			foutptr = frewrite;
18459Sjkh		} else {
184611894Speter#			if bad_creat0
184711894Speter				int nr = !!frewrite, ne = 0;
184811894Speter#			endif
184911894Speter			ORCSclose();
18509Sjkh			seteid();
18519Sjkh			ignoreints();
185211894Speter#			if bad_creat0
185311894Speter				if (nr) {
185411894Speter					nr = un_link(newRCSname);
185511894Speter					ne = errno;
185611894Speter					keepdirtemp(newRCSname);
185711894Speter				}
185811894Speter#			endif
185911894Speter			r = un_link(lockname);
18609Sjkh			e = errno;
186111894Speter			keepdirtemp(lockname);
18629Sjkh			restoreints();
18639Sjkh			setrid();
186411894Speter			if (r != 0)
186511894Speter				enerror(e, lockname);
186611894Speter#			if bad_creat0
186711894Speter				if (nr != 0) {
186811894Speter					enerror(ne, newRCSname);
186911894Speter					r = -1;
187011894Speter				}
187111894Speter#			endif
18729Sjkh		}
187311894Speter	return r;
18749Sjkh}
18759Sjkh
18769Sjkh	int
187711894Speterdonerewrite(changed, newRCStime)
18789Sjkh	int changed;
187911894Speter	time_t newRCStime;
18809Sjkh/*
18819Sjkh * Finish rewriting an RCS file if CHANGED is nonzero.
188211894Speter * Set its mode if CHANGED is positive.
188311894Speter * Set its modification time to NEWRCSTIME unless it is -1.
188411894Speter * Return 0 on success, -1 on failure.
18859Sjkh */
18869Sjkh{
188711894Speter	int r = 0, e = 0;
188811894Speter#	if bad_creat0
188911894Speter		int lr, le;
189011894Speter#	endif
18919Sjkh
18929Sjkh	if (changed && !nerror) {
18939Sjkh		if (finptr) {
18949Sjkh			fastcopy(finptr, frewrite);
18959Sjkh			Izclose(&finptr);
18969Sjkh		}
18979Sjkh		if (1 < RCSstat.st_nlink)
189811894Speter			rcswarn("breaking hard link");
189911894Speter		aflush(frewrite);
19009Sjkh		seteid();
19019Sjkh		ignoreints();
190211894Speter		r = chnamemod(
190311894Speter			&frewrite, newRCSname, RCSname, changed,
190411894Speter			RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
190511894Speter			newRCStime
19069Sjkh		);
19079Sjkh		e = errno;
190811894Speter		keepdirtemp(newRCSname);
190911894Speter#		if bad_creat0
191011894Speter			lr = un_link(lockname);
191111894Speter			le = errno;
191211894Speter			keepdirtemp(lockname);
191311894Speter#		endif
19149Sjkh		restoreints();
19159Sjkh		setrid();
19169Sjkh		if (r != 0) {
191711894Speter			enerror(e, RCSname);
191811894Speter			error("saved in %s", newRCSname);
19199Sjkh		}
192011894Speter#		if bad_creat0
192111894Speter			if (lr != 0) {
192211894Speter				enerror(le, lockname);
192311894Speter				r = -1;
192411894Speter			}
192511894Speter#		endif
19269Sjkh	}
192711894Speter	return r;
19289Sjkh}
19299Sjkh
19309Sjkh	void
193111894SpeterORCSclose()
19329Sjkh{
193311894Speter	if (0 <= fdlock) {
193411894Speter		if (close(fdlock) != 0)
193511894Speter			efaterror(lockname);
193611894Speter		fdlock = -1;
193711894Speter	}
193811894Speter	Ozclose(&frewrite);
19399Sjkh}
194011894Speter
194111894Speter	void
194211894SpeterORCSerror()
194311894Speter/*
194411894Speter* Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
194511894Speter* Do not report errors, since this may loop.  This is needed only because
194611894Speter* some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
194711894Speter* some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
194811894Speter* This isn't a completely reliable away to work around brain-damaged hosts,
194911894Speter* because of the gap between actual file opening and setting frewrite etc.,
195011894Speter* but it's better than nothing.
195111894Speter*/
195211894Speter{
195311894Speter	if (0 <= fdlock)
195411894Speter		VOID close(fdlock);
195511894Speter	if (frewrite)
195611894Speter		/* Avoid fclose, since stdio may not be reentrant.  */
195711894Speter		VOID close(fileno(frewrite));
195811894Speter}
1959