rcsedit.c revision 11894
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 * $Log: rcsedit.c,v $
3911894Speter * Revision 5.19  1995/06/16 06:19:24  eggert
4011894Speter * Update FSF address.
418858Srgrimes *
4211894Speter * Revision 5.18  1995/06/01 16:23:43  eggert
4311894Speter * (dirtpname): No longer external.
4411894Speter * (do_link): Simplify logic.
4511894Speter * (finisheditline, finishedit): Replace Iseek/Itell with what they stand for.
4611894Speter * (fopen_update_truncate): Replace `#if' with `if'.
4711894Speter * (keyreplace, makedirtemp): dirlen(x) -> basefilename(x)-x.
481494Srgrimes *
4911894Speter * (edit_string): Fix bug: if !large_memory, a bogus trailing `@' was output
5011894Speter * at the end of incomplete lines.
5111894Speter *
5211894Speter * (keyreplace): Do not assume that seeking backwards
5311894Speter * at the start of a file will fail; on some systems it succeeds.
5411894Speter * Convert C- and Pascal-style comment starts to ` *' in comment leader.
5511894Speter *
5611894Speter * (rcswriteopen): Use fdSafer to get safer file descriptor.
5711894Speter * Open RCS file with FOPEN_RB.
5811894Speter *
5911894Speter * (chnamemod): Work around bad_NFS_rename bug; don't ignore un_link result.
6011894Speter * Fall back on chmod if fchmod fails, since it might be ENOSYS.
6111894Speter *
6211894Speter * (aflush): Move to rcslex.c.
6311894Speter *
6411894Speter * Revision 5.17  1994/03/20 04:52:58  eggert
6511894Speter * Normally calculate the $Log prefix from context, not from RCS file.
6611894Speter * Move setmtime here from rcsutil.c.  Add ORCSerror.  Remove lint.
6711894Speter *
6811894Speter * Revision 5.16  1993/11/03 17:42:27  eggert
6911894Speter * Add -z.  Add Name keyword.  If bad_unlink, ignore errno when unlink fails.
7011894Speter * Escape white space, $, and \ in keyword string file names.
7111894Speter * Don't output 2 spaces between date and time after Log.
7211894Speter *
7311894Speter * Revision 5.15  1992/07/28  16:12:44  eggert
7411894Speter * Some hosts have readlink but not ELOOP.  Avoid `unsigned'.
7511894Speter * Preserve dates more systematically.  Statement macro names now end in _.
7611894Speter *
7711894Speter * Revision 5.14  1992/02/17  23:02:24  eggert
7811894Speter * Add -T support.
7911894Speter *
8011894Speter * Revision 5.13  1992/01/24  18:44:19  eggert
8111894Speter * Add support for bad_chmod_close, bad_creat0.
8211894Speter *
8311894Speter * Revision 5.12  1992/01/06  02:42:34  eggert
8411894Speter * Add setmode parameter to chnamemod.  addsymbol now reports changes.
8511894Speter * while (E) ; -> while (E) continue;
8611894Speter *
879Sjkh * Revision 5.11  1991/11/03  01:11:44  eggert
889Sjkh * Move the warning about link breaking to where they're actually being broken.
899Sjkh *
909Sjkh * Revision 5.10  1991/10/07  17:32:46  eggert
919Sjkh * Support piece tables even if !has_mmap.  Fix rare NFS bugs.
929Sjkh *
939Sjkh * Revision 5.9  1991/09/17  19:07:40  eggert
949Sjkh * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
959Sjkh *
969Sjkh * Revision 5.8  1991/08/19  03:13:55  eggert
979Sjkh * Add piece tables, NFS bug workarounds.  Catch odd filenames.  Tune.
989Sjkh *
999Sjkh * Revision 5.7  1991/04/21  11:58:21  eggert
1009Sjkh * Fix errno bugs.  Add -x, RCSINIT, MS-DOS support.
1019Sjkh *
1029Sjkh * Revision 5.6  1991/02/25  07:12:40  eggert
1039Sjkh * Fix setuid bug.  Support new link behavior.  Work around broken "w+" fopen.
1049Sjkh *
1059Sjkh * Revision 5.5  1990/12/30  05:07:35  eggert
1069Sjkh * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL).
1079Sjkh *
1089Sjkh * Revision 5.4  1990/11/01  05:03:40  eggert
1099Sjkh * Permit arbitrary data in comment leaders.
1109Sjkh *
1119Sjkh * Revision 5.3  1990/09/11  02:41:13  eggert
1129Sjkh * Tune expandline().
1139Sjkh *
1149Sjkh * Revision 5.2  1990/09/04  08:02:21  eggert
1159Sjkh * Count RCS lines better.  Improve incomplete line handling.
1169Sjkh *
1179Sjkh * Revision 5.1  1990/08/29  07:13:56  eggert
1189Sjkh * Add -kkvl.
1199Sjkh * Fix bug when getting revisions to files ending in incomplete lines.
1209Sjkh * Fix bug in comment leader expansion.
1219Sjkh *
1229Sjkh * Revision 5.0  1990/08/22  08:12:47  eggert
1239Sjkh * Don't require final newline.
1249Sjkh * Don't append "checked in with -k by " to logs,
1259Sjkh * so that checking in a program with -k doesn't change it.
1269Sjkh * Don't generate trailing white space for empty comment leader.
1279Sjkh * Remove compile-time limits; use malloc instead.  Add -k, -V.
1289Sjkh * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
1299Sjkh * Ansify and Posixate.  Check diff's output.
1309Sjkh *
1319Sjkh * Revision 4.8  89/05/01  15:12:35  narten
1329Sjkh * changed copyright header to reflect current distribution rules
1338858Srgrimes *
1349Sjkh * Revision 4.7  88/11/08  13:54:14  narten
1359Sjkh * misplaced semicolon caused infinite loop
1368858Srgrimes *
1379Sjkh * Revision 4.6  88/08/09  19:12:45  eggert
1389Sjkh * Shrink stdio code size; allow cc -R.
1398858Srgrimes *
1409Sjkh * Revision 4.5  87/12/18  11:38:46  narten
1419Sjkh * Changes from the 43. version. Don't know the significance of the
1429Sjkh * first change involving "rewind". Also, additional "lint" cleanup.
1439Sjkh * (Guy Harris)
1448858Srgrimes *
1459Sjkh * Revision 4.4  87/10/18  10:32:21  narten
1469Sjkh * Updating version numbers. Changes relative to version 1.1 actually
1479Sjkh * relative to 4.1
1488858Srgrimes *
1499Sjkh * Revision 1.4  87/09/24  13:59:29  narten
1508858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf
1519Sjkh * warnings)
1528858Srgrimes *
1539Sjkh * Revision 1.3  87/09/15  16:39:39  shepler
1549Sjkh * added an initializatin of the variables editline and linecorr
1559Sjkh * this will be done each time a file is processed.
1569Sjkh * (there was an obscure bug where if co was used to retrieve multiple files
1579Sjkh *  it would dump)
1589Sjkh * fix attributed to  Roy Morris @FileNet Corp ...!felix!roy
1598858Srgrimes *
1609Sjkh * Revision 1.2  87/03/27  14:22:17  jenkins
1619Sjkh * Port to suns
1628858Srgrimes *
1639Sjkh * Revision 4.1  83/05/12  13:10:30  wft
1649Sjkh * Added new markers Id and RCSfile; added locker to Header and Id.
1659Sjkh * Overhauled expandline completely() (problem with $01234567890123456789@).
1669Sjkh * Moved trymatch() and marker table to rcskeys.c.
1678858Srgrimes *
1689Sjkh * Revision 3.7  83/05/12  13:04:39  wft
1699Sjkh * Added retry to expandline to resume after failed match which ended in $.
1709Sjkh * Fixed truncation problem for $19chars followed by@@.
1719Sjkh * Log no longer expands full path of RCS file.
1728858Srgrimes *
1739Sjkh * Revision 3.6  83/05/11  16:06:30  wft
1749Sjkh * added retry to expandline to resume after failed match which ended in $.
1759Sjkh * Fixed truncation problem for $19chars followed by@@.
1768858Srgrimes *
1779Sjkh * Revision 3.5  82/12/04  13:20:56  wft
1789Sjkh * Added expansion of keyword Locker.
1799Sjkh *
1809Sjkh * Revision 3.4  82/12/03  12:26:54  wft
1819Sjkh * Added line number correction in case editing does not start at the
1829Sjkh * beginning of the file.
1839Sjkh * Changed keyword expansion to always print a space before closing KDELIM;
1849Sjkh * Expansion for Header shortened.
1859Sjkh *
1869Sjkh * Revision 3.3  82/11/14  14:49:30  wft
1879Sjkh * removed Suffix from keyword expansion. Replaced fclose with ffclose.
1889Sjkh * keyreplace() gets log message from delta, not from curlogmsg.
1899Sjkh * fixed expression overflow in while(c=putc(GETC....
1909Sjkh * checked nil printing.
1919Sjkh *
1929Sjkh * Revision 3.2  82/10/18  21:13:39  wft
1939Sjkh * I added checks for write errors during the co process, and renamed
1949Sjkh * expandstring() to xpandstring().
1959Sjkh *
1969Sjkh * Revision 3.1  82/10/13  15:52:55  wft
1979Sjkh * changed type of result of getc() from char to int.
1989Sjkh * made keyword expansion loop in expandline() portable to machines
1999Sjkh * without sign-extension.
2009Sjkh */
2019Sjkh
2029Sjkh
2039Sjkh#include "rcsbase.h"
2049Sjkh
20511894SpeterlibId(editId, "$Id: rcsedit.c,v 5.19 1995/06/16 06:19:24 eggert Exp $")
2069Sjkh
20711894Speterstatic void editEndsPrematurely P((void)) exiting;
20811894Speterstatic void editLineNumberOverflow P((void)) exiting;
20911894Speterstatic void escape_string P((FILE*,char const*));
21011894Speterstatic void keyreplace P((enum markers,struct hshentry const*,int,RILE*,FILE*,int));
2119Sjkh
2129SjkhFILE *fcopy;		 /* result file descriptor			    */
21311894Speterchar const *resultname;	 /* result pathname				    */
2149Sjkhint locker_expansion;	 /* should the locker name be appended to Id val?   */
2159Sjkh#if !large_memory
2169Sjkh	static RILE *fedit; /* edit file descriptor */
21711894Speter	static char const *editname; /* edit pathname */
2189Sjkh#endif
21911894Speterstatic long editline; /* edit line counter; #lines before cursor   */
2209Sjkhstatic long linecorr; /* #adds - #deletes in each edit run.		    */
2219Sjkh               /*used to correct editline in case file is not rewound after */
2229Sjkh               /* applying one delta                                        */
2239Sjkh
22411894Speter/* indexes into dirtpname */
22511894Speter#define lockdirtp_index 0
22611894Speter#define newRCSdirtp_index bad_creat0
22711894Speter#define newworkdirtp_index (newRCSdirtp_index+1)
22811894Speter#define DIRTEMPNAMES (newworkdirtp_index + 1)
22911894Speter
2309Sjkhenum maker {notmade, real, effective};
23111894Speterstatic struct buf dirtpname[DIRTEMPNAMES];	/* unlink these when done */
23211894Speterstatic enum maker volatile dirtpmaker[DIRTEMPNAMES];	/* if these are set */
23311894Speter#define lockname (dirtpname[lockdirtp_index].string)
23411894Speter#define newRCSname (dirtpname[newRCSdirtp_index].string)
2359Sjkh
2369Sjkh
2379Sjkh#if has_NFS || bad_unlink
2389Sjkh	int
2399Sjkhun_link(s)
2409Sjkh	char const *s;
2419Sjkh/*
2429Sjkh * Remove S, even if it is unwritable.
2439Sjkh * Ignore unlink() ENOENT failures; NFS generates bogus ones.
2449Sjkh */
2459Sjkh{
2469Sjkh#	if bad_unlink
2479Sjkh		if (unlink(s) == 0)
2489Sjkh			return 0;
24911894Speter		else {
25011894Speter			int e = errno;
25111894Speter			/*
25211894Speter			* Forge ahead even if errno == ENOENT; some completely
25311894Speter			* brain-damaged hosts (e.g. PCTCP 2.2) yield ENOENT
25411894Speter			* even for existing unwritable files.
25511894Speter			*/
25611894Speter			if (chmod(s, S_IWUSR) != 0) {
25711894Speter				errno = e;
25811894Speter				return -1;
25911894Speter			}
2609Sjkh		}
2619Sjkh#	endif
2629Sjkh#	if has_NFS
2639Sjkh		return unlink(s)==0 || errno==ENOENT  ?  0  :  -1;
2649Sjkh#	else
2659Sjkh		return unlink(s);
2669Sjkh#	endif
2679Sjkh}
2689Sjkh#endif
2699Sjkh
2709Sjkh#if !has_rename
2719Sjkh#  if !has_NFS
2729Sjkh#	define do_link(s,t) link(s,t)
2739Sjkh#  else
27411894Speter	static int do_link P((char const*,char const*));
2759Sjkh	static int
2769Sjkhdo_link(s, t)
2779Sjkh	char const *s, *t;
2789Sjkh/* Link S to T, ignoring bogus EEXIST problems due to NFS failures.  */
2799Sjkh{
28011894Speter	int r = link(s, t);
2819Sjkh
28211894Speter	if (r != 0  &&  errno == EEXIST) {
28311894Speter		struct stat sb, tb;
28411894Speter		if (
28511894Speter		    stat(s, &sb) == 0  &&
28611894Speter		    stat(t, &tb) == 0  &&
28711894Speter		    same_file(sb, tb, 0)
28811894Speter		)
28911894Speter			r = 0;
29011894Speter		errno = EEXIST;
29111894Speter	}
29211894Speter	return r;
2939Sjkh}
2949Sjkh#  endif
2959Sjkh#endif
2969Sjkh
2979Sjkh
29811894Speter	static void
2999SjkheditEndsPrematurely()
3009Sjkh{
3019Sjkh	fatserror("edit script ends prematurely");
3029Sjkh}
3039Sjkh
30411894Speter	static void
3059SjkheditLineNumberOverflow()
3069Sjkh{
3079Sjkh	fatserror("edit script refers to line past end of file");
3089Sjkh}
3099Sjkh
3109Sjkh
3119Sjkh#if large_memory
3129Sjkh
3139Sjkh#if has_memmove
3149Sjkh#	define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
3159Sjkh#else
31611894Speter	static void movelines P((Iptr_type*,Iptr_type const*,long));
3179Sjkh	static void
3189Sjkhmovelines(s1, s2, n)
3199Sjkh	register Iptr_type *s1;
3209Sjkh	register Iptr_type const *s2;
32111894Speter	register long n;
3229Sjkh{
3239Sjkh	if (s1 < s2)
3249Sjkh		do {
3259Sjkh			*s1++ = *s2++;
3269Sjkh		} while (--n);
3279Sjkh	else {
3289Sjkh		s1 += n;
3299Sjkh		s2 += n;
3309Sjkh		do {
3319Sjkh			*--s1 = *--s2;
3329Sjkh		} while (--n);
3339Sjkh	}
3349Sjkh}
3359Sjkh#endif
3369Sjkh
33711894Speterstatic void deletelines P((long,long));
33811894Speterstatic void finisheditline P((RILE*,FILE*,Iptr_type,struct hshentry const*));
33911894Speterstatic void insertline P((long,Iptr_type));
34011894Speterstatic void snapshotline P((FILE*,Iptr_type));
34111894Speter
3429Sjkh/*
3439Sjkh * `line' contains pointers to the lines in the currently `edited' file.
3449Sjkh * It is a 0-origin array that represents linelim-gapsize lines.
34511894Speter * line[0 .. gap-1] and line[gap+gapsize .. linelim-1] hold pointers to lines.
34611894Speter * line[gap .. gap+gapsize-1] contains garbage.
3479Sjkh *
3489Sjkh * Any @s in lines are duplicated.
3499Sjkh * Lines are terminated by \n, or (for a last partial line only) by single @.
3509Sjkh */
3519Sjkhstatic Iptr_type *line;
35211894Speterstatic size_t gap, gapsize, linelim;
3539Sjkh
3549Sjkh	static void
3559Sjkhinsertline(n, l)
35611894Speter	long n;
3579Sjkh	Iptr_type l;
3589Sjkh/* Before line N, insert line L.  N is 0-origin.  */
3599Sjkh{
3609Sjkh	if (linelim-gapsize < n)
3619Sjkh	    editLineNumberOverflow();
3629Sjkh	if (!gapsize)
3639Sjkh	    line =
3649Sjkh		!linelim ?
3659Sjkh			tnalloc(Iptr_type, linelim = gapsize = 1024)
3669Sjkh		: (
3679Sjkh			gap = gapsize = linelim,
3689Sjkh			trealloc(Iptr_type, line, linelim <<= 1)
3699Sjkh		);
3709Sjkh	if (n < gap)
3719Sjkh	    movelines(line+n+gapsize, line+n, gap-n);
3729Sjkh	else if (gap < n)
3739Sjkh	    movelines(line+gap, line+gap+gapsize, n-gap);
3749Sjkh
3759Sjkh	line[n] = l;
3769Sjkh	gap = n + 1;
3779Sjkh	gapsize--;
3789Sjkh}
3799Sjkh
3809Sjkh	static void
3819Sjkhdeletelines(n, nlines)
38211894Speter	long n, nlines;
3839Sjkh/* Delete lines N through N+NLINES-1.  N is 0-origin.  */
3849Sjkh{
38511894Speter	long l = n + nlines;
3869Sjkh	if (linelim-gapsize < l  ||  l < n)
3879Sjkh	    editLineNumberOverflow();
3889Sjkh	if (l < gap)
3899Sjkh	    movelines(line+l+gapsize, line+l, gap-l);
3909Sjkh	else if (gap < n)
3919Sjkh	    movelines(line+gap, line+gap+gapsize, n-gap);
3929Sjkh
3939Sjkh	gap = n;
3949Sjkh	gapsize += nlines;
3959Sjkh}
3969Sjkh
3979Sjkh	static void
3989Sjkhsnapshotline(f, l)
3999Sjkh	register FILE *f;
4009Sjkh	register Iptr_type l;
4019Sjkh{
4029Sjkh	register int c;
4039Sjkh	do {
4049Sjkh		if ((c = *l++) == SDELIM  &&  *l++ != SDELIM)
4059Sjkh			return;
40611894Speter		aputc_(c, f)
4079Sjkh	} while (c != '\n');
4089Sjkh}
4099Sjkh
4109Sjkh	void
4119Sjkhsnapshotedit(f)
4129Sjkh	FILE *f;
4139Sjkh/* Copy the current state of the edits to F.  */
4149Sjkh{
4159Sjkh	register Iptr_type *p, *lim, *l=line;
4169Sjkh	for (p=l, lim=l+gap;  p<lim;  )
4179Sjkh		snapshotline(f, *p++);
4189Sjkh	for (p+=gapsize, lim=l+linelim;  p<lim;  )
4199Sjkh		snapshotline(f, *p++);
4209Sjkh}
4219Sjkh
4229Sjkh	static void
4239Sjkhfinisheditline(fin, fout, l, delta)
4249Sjkh	RILE *fin;
4259Sjkh	FILE *fout;
4269Sjkh	Iptr_type l;
4279Sjkh	struct hshentry const *delta;
4289Sjkh{
42911894Speter	fin->ptr = l;
43011894Speter	if (expandline(fin, fout, delta, true, (FILE*)0, true)  <  0)
4319Sjkh		faterror("finisheditline internal error");
4329Sjkh}
4339Sjkh
4349Sjkh	void
4359Sjkhfinishedit(delta, outfile, done)
4369Sjkh	struct hshentry const *delta;
4379Sjkh	FILE *outfile;
4389Sjkh	int done;
4399Sjkh/*
4409Sjkh * Doing expansion if DELTA is set, output the state of the edits to OUTFILE.
4419Sjkh * But do nothing unless DONE is set (which means we are on the last pass).
4429Sjkh */
4439Sjkh{
4449Sjkh	if (done) {
4459Sjkh		openfcopy(outfile);
4469Sjkh		outfile = fcopy;
4479Sjkh		if (!delta)
4489Sjkh			snapshotedit(outfile);
4499Sjkh		else {
4509Sjkh			register Iptr_type *p, *lim, *l = line;
4519Sjkh			register RILE *fin = finptr;
45211894Speter			Iptr_type here = fin->ptr;
4539Sjkh			for (p=l, lim=l+gap;  p<lim;  )
4549Sjkh				finisheditline(fin, outfile, *p++, delta);
4559Sjkh			for (p+=gapsize, lim=l+linelim;  p<lim;  )
4569Sjkh				finisheditline(fin, outfile, *p++, delta);
45711894Speter			fin->ptr = here;
4589Sjkh		}
4599Sjkh	}
4609Sjkh}
4619Sjkh
46211894Speter/* Open a temporary NAME for output, truncating any previous contents.  */
46311894Speter#   define fopen_update_truncate(name) fopenSafer(name, FOPEN_W_WORK)
4649Sjkh#else /* !large_memory */
46511894Speter    static FILE * fopen_update_truncate P((char const*));
4669Sjkh    static FILE *
46711894Speterfopen_update_truncate(name)
46811894Speter    char const *name;
4699Sjkh{
47011894Speter	if (bad_fopen_wplus  &&  un_link(name) != 0)
47111894Speter		efaterror(name);
47211894Speter	return fopenSafer(name, FOPEN_WPLUS_WORK);
4739Sjkh}
4749Sjkh#endif
4759Sjkh
4769Sjkh
4779Sjkh	void
4789Sjkhopenfcopy(f)
4799Sjkh	FILE *f;
4809Sjkh{
4819Sjkh	if (!(fcopy = f)) {
48211894Speter		if (!resultname)
48311894Speter			resultname = maketemp(2);
48411894Speter		if (!(fcopy = fopen_update_truncate(resultname)))
48511894Speter			efaterror(resultname);
4869Sjkh	}
4879Sjkh}
4889Sjkh
4899Sjkh
4909Sjkh#if !large_memory
4919Sjkh
49211894Speter	static void swapeditfiles P((FILE*));
4939Sjkh	static void
4949Sjkhswapeditfiles(outfile)
4959Sjkh	FILE *outfile;
49611894Speter/* Function: swaps resultname and editname, assigns fedit=fcopy,
4979Sjkh * and rewinds fedit for reading.  Set fcopy to outfile if nonnull;
49811894Speter * otherwise, set fcopy to be resultname opened for reading and writing.
4999Sjkh */
5009Sjkh{
5019Sjkh	char const *tmpptr;
5029Sjkh
5039Sjkh	editline = 0;  linecorr = 0;
50411894Speter	Orewind(fcopy);
5059Sjkh	fedit = fcopy;
50611894Speter	tmpptr=editname; editname=resultname; resultname=tmpptr;
5079Sjkh	openfcopy(outfile);
5089Sjkh}
5099Sjkh
5109Sjkh	void
5119Sjkhsnapshotedit(f)
5129Sjkh	FILE *f;
5139Sjkh/* Copy the current state of the edits to F.  */
5149Sjkh{
51511894Speter	finishedit((struct hshentry *)0, (FILE*)0, false);
5169Sjkh	fastcopy(fedit, f);
5179Sjkh	Irewind(fedit);
5189Sjkh}
5199Sjkh
5209Sjkh	void
5219Sjkhfinishedit(delta, outfile, done)
5229Sjkh	struct hshentry const *delta;
5239Sjkh	FILE *outfile;
5249Sjkh	int done;
5259Sjkh/* copy the rest of the edit file and close it (if it exists).
52611894Speter * if delta, perform keyword substitution at the same time.
5279Sjkh * If DONE is set, we are finishing the last pass.
5289Sjkh */
5299Sjkh{
5309Sjkh	register RILE *fe;
5319Sjkh	register FILE *fc;
5329Sjkh
5339Sjkh	fe = fedit;
5349Sjkh	if (fe) {
5359Sjkh		fc = fcopy;
53611894Speter		if (delta) {
53711894Speter			while (1 < expandline(fe,fc,delta,false,(FILE*)0,true))
5389Sjkh				;
5399Sjkh                } else {
5409Sjkh			fastcopy(fe,fc);
5419Sjkh                }
5429Sjkh		Ifclose(fe);
5439Sjkh        }
5449Sjkh	if (!done)
5459Sjkh		swapeditfiles(outfile);
5469Sjkh}
5479Sjkh#endif
5489Sjkh
5499Sjkh
5509Sjkh
5519Sjkh#if large_memory
5529Sjkh#	define copylines(upto,delta) (editline = (upto))
5539Sjkh#else
55411894Speter	static void copylines P((long,struct hshentry const*));
5559Sjkh	static void
55611894Spetercopylines(upto, delta)
55711894Speter	register long upto;
5589Sjkh	struct hshentry const *delta;
5599Sjkh/*
5609Sjkh * Copy input lines editline+1..upto from fedit to fcopy.
56111894Speter * If delta, keyword expansion is done simultaneously.
5629Sjkh * editline is updated. Rewinds a file only if necessary.
5639Sjkh */
5649Sjkh{
5659Sjkh	register int c;
5669Sjkh	declarecache;
5679Sjkh	register FILE *fc;
5689Sjkh	register RILE *fe;
5699Sjkh
5709Sjkh	if (upto < editline) {
5719Sjkh                /* swap files */
57211894Speter		finishedit((struct hshentry *)0, (FILE*)0, false);
5739Sjkh                /* assumes edit only during last pass, from the beginning*/
5749Sjkh        }
5759Sjkh	fe = fedit;
5769Sjkh	fc = fcopy;
5779Sjkh	if (editline < upto)
5789Sjkh	    if (delta)
5799Sjkh		do {
58011894Speter		    if (expandline(fe,fc,delta,false,(FILE*)0,true) <= 1)
58111894Speter			editLineNumberOverflow();
5829Sjkh		} while (++editline < upto);
5839Sjkh	    else {
5849Sjkh		setupcache(fe); cache(fe);
5859Sjkh		do {
5869Sjkh			do {
58711894Speter				cachegeteof_(c, editLineNumberOverflow();)
58811894Speter				aputc_(c, fc)
5899Sjkh			} while (c != '\n');
5909Sjkh		} while (++editline < upto);
5919Sjkh		uncache(fe);
5929Sjkh	    }
5939Sjkh}
5949Sjkh#endif
5959Sjkh
5969Sjkh
5979Sjkh
5989Sjkh	void
5999Sjkhxpandstring(delta)
6009Sjkh	struct hshentry const *delta;
6019Sjkh/* Function: Reads a string terminated by SDELIM from finptr and writes it
6029Sjkh * to fcopy. Double SDELIM is replaced with single SDELIM.
6039Sjkh * Keyword expansion is performed with data from delta.
6049Sjkh * If foutptr is nonnull, the string is also copied unchanged to foutptr.
6059Sjkh */
6069Sjkh{
60711894Speter	while (1 < expandline(finptr,fcopy,delta,true,foutptr,true))
60811894Speter		continue;
6099Sjkh}
6109Sjkh
6119Sjkh
6129Sjkh	void
6139Sjkhcopystring()
6149Sjkh/* Function: copies a string terminated with a single SDELIM from finptr to
6159Sjkh * fcopy, replacing all double SDELIM with a single SDELIM.
6169Sjkh * If foutptr is nonnull, the string also copied unchanged to foutptr.
6179Sjkh * editline is incremented by the number of lines copied.
6189Sjkh * Assumption: next character read is first string character.
6199Sjkh */
6209Sjkh{	register c;
6219Sjkh	declarecache;
6229Sjkh	register FILE *frew, *fcop;
6239Sjkh	register int amidline;
6249Sjkh	register RILE *fin;
6259Sjkh
6269Sjkh	fin = finptr;
6279Sjkh	setupcache(fin); cache(fin);
6289Sjkh	frew = foutptr;
6299Sjkh	fcop = fcopy;
6309Sjkh	amidline = false;
6319Sjkh	for (;;) {
63211894Speter		GETC_(frew,c)
6339Sjkh		switch (c) {
6349Sjkh		    case '\n':
6359Sjkh			++editline;
6369Sjkh			++rcsline;
6379Sjkh			amidline = false;
6389Sjkh			break;
6399Sjkh		    case SDELIM:
64011894Speter			GETC_(frew,c)
6419Sjkh			if (c != SDELIM) {
6429Sjkh				/* end of string */
6439Sjkh				nextc = c;
6449Sjkh				editline += amidline;
6459Sjkh				uncache(fin);
6469Sjkh				return;
6479Sjkh			}
6489Sjkh			/* fall into */
6499Sjkh		    default:
6509Sjkh			amidline = true;
6519Sjkh			break;
6529Sjkh                }
65311894Speter		aputc_(c,fcop)
6549Sjkh        }
6559Sjkh}
6569Sjkh
6579Sjkh
6589Sjkh	void
6599Sjkhenterstring()
6609Sjkh/* Like copystring, except the string is put into the edit data structure.  */
6619Sjkh{
6629Sjkh#if !large_memory
66311894Speter	editname = 0;
6649Sjkh	fedit = 0;
6659Sjkh	editline = linecorr = 0;
66611894Speter	resultname = maketemp(1);
66711894Speter	if (!(fcopy = fopen_update_truncate(resultname)))
66811894Speter		efaterror(resultname);
6699Sjkh	copystring();
6709Sjkh#else
6719Sjkh	register int c;
6729Sjkh	declarecache;
6739Sjkh	register FILE *frew;
67411894Speter	register long e, oe;
6759Sjkh	register int amidline, oamidline;
6769Sjkh	register Iptr_type optr;
6779Sjkh	register RILE *fin;
6789Sjkh
6799Sjkh	e = 0;
6809Sjkh	gap = 0;
6819Sjkh	gapsize = linelim;
6829Sjkh	fin = finptr;
6839Sjkh	setupcache(fin); cache(fin);
6849Sjkh	advise_access(fin, MADV_NORMAL);
6859Sjkh	frew = foutptr;
6869Sjkh	amidline = false;
6879Sjkh	for (;;) {
68811894Speter		optr = cacheptr();
68911894Speter		GETC_(frew,c)
6909Sjkh		oamidline = amidline;
6919Sjkh		oe = e;
6929Sjkh		switch (c) {
6939Sjkh		    case '\n':
6949Sjkh			++e;
6959Sjkh			++rcsline;
6969Sjkh			amidline = false;
6979Sjkh			break;
6989Sjkh		    case SDELIM:
69911894Speter			GETC_(frew,c)
7009Sjkh			if (c != SDELIM) {
7019Sjkh				/* end of string */
7029Sjkh				nextc = c;
7039Sjkh				editline = e + amidline;
7049Sjkh				linecorr = 0;
7059Sjkh				uncache(fin);
7069Sjkh				return;
7079Sjkh			}
7089Sjkh			/* fall into */
7099Sjkh		    default:
7109Sjkh			amidline = true;
7119Sjkh			break;
7129Sjkh		}
7139Sjkh		if (!oamidline)
7149Sjkh			insertline(oe, optr);
7159Sjkh	}
7169Sjkh#endif
7179Sjkh}
7189Sjkh
7199Sjkh
7209Sjkh
7219Sjkh
7229Sjkh	void
7239Sjkh#if large_memory
7249Sjkhedit_string()
7259Sjkh#else
7269Sjkh  editstring(delta)
7279Sjkh	struct hshentry const *delta;
7289Sjkh#endif
7299Sjkh/*
7309Sjkh * Read an edit script from finptr and applies it to the edit file.
7319Sjkh#if !large_memory
7329Sjkh * The result is written to fcopy.
73311894Speter * If delta, keyword expansion is performed simultaneously.
7349Sjkh * If running out of lines in fedit, fedit and fcopy are swapped.
73511894Speter * editname is the name of the file that goes with fedit.
7369Sjkh#endif
7379Sjkh * If foutptr is set, the edit script is also copied verbatim to foutptr.
7389Sjkh * Assumes that all these files are open.
73911894Speter * resultname is the name of the file that goes with fcopy.
7409Sjkh * Assumes the next input character from finptr is the first character of
7419Sjkh * the edit script. Resets nextc on exit.
7429Sjkh */
7439Sjkh{
7449Sjkh        int ed; /* editor command */
7459Sjkh        register int c;
7469Sjkh	declarecache;
7479Sjkh	register FILE *frew;
7489Sjkh#	if !large_memory
7499Sjkh		register FILE *f;
75011894Speter		long line_lim = LONG_MAX;
7519Sjkh		register RILE *fe;
7529Sjkh#	endif
75311894Speter	register long i;
7549Sjkh	register RILE *fin;
7559Sjkh#	if large_memory
75611894Speter		register long j;
7579Sjkh#	endif
7589Sjkh	struct diffcmd dc;
7599Sjkh
7609Sjkh        editline += linecorr; linecorr=0; /*correct line number*/
7619Sjkh	frew = foutptr;
7629Sjkh	fin = finptr;
7639Sjkh	setupcache(fin);
7649Sjkh	initdiffcmd(&dc);
7659Sjkh	while (0  <=  (ed = getdiffcmd(fin,true,frew,&dc)))
7669Sjkh#if !large_memory
7679Sjkh		if (line_lim <= dc.line1)
7689Sjkh			editLineNumberOverflow();
7699Sjkh		else
7709Sjkh#endif
7719Sjkh		if (!ed) {
7729Sjkh			copylines(dc.line1-1, delta);
7739Sjkh                        /* skip over unwanted lines */
7749Sjkh			i = dc.nlines;
7759Sjkh			linecorr -= i;
7769Sjkh			editline += i;
7779Sjkh#			if large_memory
7789Sjkh			    deletelines(editline+linecorr, i);
7799Sjkh#			else
7809Sjkh			    fe = fedit;
7819Sjkh			    do {
7829Sjkh                                /*skip next line*/
7839Sjkh				do {
78411894Speter				    Igeteof_(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } )
7859Sjkh				} while (c != '\n');
7869Sjkh			    } while (--i);
7879Sjkh#			endif
7889Sjkh		} else {
78911894Speter			/* Copy lines without deleting any.  */
79011894Speter			copylines(dc.line1, delta);
7919Sjkh			i = dc.nlines;
7929Sjkh#			if large_memory
7939Sjkh				j = editline+linecorr;
7949Sjkh#			endif
7959Sjkh			linecorr += i;
7969Sjkh#if !large_memory
7979Sjkh			f = fcopy;
7989Sjkh			if (delta)
7999Sjkh			    do {
80011894Speter				switch (expandline(fin,f,delta,true,frew,true)){
8019Sjkh				    case 0: case 1:
8029Sjkh					if (i==1)
8039Sjkh					    return;
8049Sjkh					/* fall into */
8059Sjkh				    case -1:
8069Sjkh					editEndsPrematurely();
8079Sjkh				}
8089Sjkh			    } while (--i);
8099Sjkh			else
8109Sjkh#endif
8119Sjkh			{
8129Sjkh			    cache(fin);
8139Sjkh			    do {
8149Sjkh#				if large_memory
81511894Speter				    insertline(j++, cacheptr());
8169Sjkh#				endif
8179Sjkh				for (;;) {
81811894Speter				    GETC_(frew, c)
8199Sjkh				    if (c==SDELIM) {
82011894Speter					GETC_(frew, c)
8219Sjkh					if (c!=SDELIM) {
8229Sjkh					    if (--i)
8239Sjkh						editEndsPrematurely();
8249Sjkh					    nextc = c;
8259Sjkh					    uncache(fin);
8269Sjkh					    return;
8279Sjkh					}
8289Sjkh				    }
82911894Speter#				    if !large_memory
83011894Speter					aputc_(c, f)
83111894Speter#				    endif
83211894Speter				    if (c == '\n')
83311894Speter					break;
8349Sjkh				}
8359Sjkh				++rcsline;
8369Sjkh			    } while (--i);
8379Sjkh			    uncache(fin);
8389Sjkh			}
8399Sjkh                }
8409Sjkh}
8419Sjkh
8429Sjkh
8439Sjkh
8449Sjkh/* The rest is for keyword expansion */
8459Sjkh
8469Sjkh
8479Sjkh
8489Sjkh	int
84911894Speterexpandline(infile, outfile, delta, delimstuffed, frewfile, dolog)
8509Sjkh	RILE *infile;
8519Sjkh	FILE *outfile, *frewfile;
8529Sjkh	struct hshentry const *delta;
85311894Speter	int delimstuffed, dolog;
8549Sjkh/*
8559Sjkh * Read a line from INFILE and write it to OUTFILE.
85611894Speter * Do keyword expansion with data from DELTA.
8579Sjkh * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
8589Sjkh * If FREWFILE is set, copy the line unchanged to FREWFILE.
8599Sjkh * DELIMSTUFFED must be true if FREWFILE is set.
86011894Speter * Append revision history to log only if DOLOG is set.
8619Sjkh * Yields -1 if no data is copied, 0 if an incomplete line is copied,
8629Sjkh * 2 if a complete line is copied; adds 1 to yield if expansion occurred.
8639Sjkh */
8649Sjkh{
8659Sjkh	register c;
8669Sjkh	declarecache;
8679Sjkh	register FILE *out, *frew;
8689Sjkh	register char * tp;
8699Sjkh	register int e, ds, r;
8709Sjkh	char const *tlim;
8719Sjkh	static struct buf keyval;
8729Sjkh        enum markers matchresult;
8739Sjkh
8749Sjkh	setupcache(infile); cache(infile);
8759Sjkh	out = outfile;
8769Sjkh	frew = frewfile;
8779Sjkh	ds = delimstuffed;
8789Sjkh	bufalloc(&keyval, keylength+3);
8799Sjkh	e = 0;
8809Sjkh	r = -1;
8819Sjkh
8829Sjkh        for (;;) {
88311894Speter	    if (ds)
88411894Speter		GETC_(frew, c)
88511894Speter	    else
88611894Speter		cachegeteof_(c, goto uncache_exit;)
8879Sjkh	    for (;;) {
8889Sjkh		switch (c) {
8899Sjkh		    case SDELIM:
8909Sjkh			if (ds) {
89111894Speter			    GETC_(frew, c)
8929Sjkh			    if (c != SDELIM) {
8939Sjkh                                /* end of string */
8949Sjkh                                nextc=c;
8959Sjkh				goto uncache_exit;
8969Sjkh			    }
8979Sjkh			}
8989Sjkh			/* fall into */
8999Sjkh		    default:
90011894Speter			aputc_(c,out)
9019Sjkh			r = 0;
9029Sjkh			break;
9039Sjkh
9049Sjkh		    case '\n':
9059Sjkh			rcsline += ds;
90611894Speter			aputc_(c,out)
9079Sjkh			r = 2;
9089Sjkh			goto uncache_exit;
9099Sjkh
9109Sjkh		    case KDELIM:
9119Sjkh			r = 0;
9129Sjkh                        /* check for keyword */
9139Sjkh                        /* first, copy a long enough string into keystring */
9149Sjkh			tp = keyval.string;
9159Sjkh			*tp++ = KDELIM;
9169Sjkh			for (;;) {
91711894Speter			    if (ds)
91811894Speter				GETC_(frew, c)
91911894Speter			    else
92011894Speter				cachegeteof_(c, goto keystring_eof;)
92111894Speter			    if (tp <= &keyval.string[keylength])
9229Sjkh				switch (ctab[c]) {
9239Sjkh				    case LETTER: case Letter:
9249Sjkh					*tp++ = c;
9259Sjkh					continue;
9269Sjkh				    default:
9279Sjkh					break;
9289Sjkh				}
9299Sjkh			    break;
9309Sjkh                        }
9319Sjkh			*tp++ = c; *tp = '\0';
9329Sjkh			matchresult = trymatch(keyval.string+1);
9339Sjkh			if (matchresult==Nomatch) {
9349Sjkh				tp[-1] = 0;
9359Sjkh				aputs(keyval.string, out);
9369Sjkh				continue;   /* last c handled properly */
9379Sjkh			}
9389Sjkh
9399Sjkh			/* Now we have a keyword terminated with a K/VDELIM */
9409Sjkh			if (c==VDELIM) {
9419Sjkh			      /* try to find closing KDELIM, and replace value */
9429Sjkh			      tlim = keyval.string + keyval.size;
9439Sjkh			      for (;;) {
94411894Speter				      if (ds)
94511894Speter					GETC_(frew, c)
94611894Speter				      else
94711894Speter					cachegeteof_(c, goto keystring_eof;)
9489Sjkh				      if (c=='\n' || c==KDELIM)
9499Sjkh					break;
9509Sjkh				      *tp++ =c;
9519Sjkh				      if (tlim <= tp)
9529Sjkh					  tp = bufenlarge(&keyval, &tlim);
9539Sjkh				      if (c==SDELIM && ds) { /*skip next SDELIM */
95411894Speter						GETC_(frew, c)
9559Sjkh						if (c != SDELIM) {
9569Sjkh							/* end of string before closing KDELIM or newline */
9579Sjkh							nextc = c;
9589Sjkh							goto keystring_eof;
9599Sjkh						}
9609Sjkh				      }
9619Sjkh			      }
9629Sjkh			      if (c!=KDELIM) {
9639Sjkh				    /* couldn't find closing KDELIM -- give up */
9649Sjkh				    *tp = 0;
9659Sjkh				    aputs(keyval.string, out);
9669Sjkh				    continue;   /* last c handled properly */
9679Sjkh			      }
9689Sjkh			}
9699Sjkh			/* now put out the new keyword value */
97011894Speter			uncache(infile);
97111894Speter			keyreplace(matchresult, delta, ds, infile, out, dolog);
97211894Speter			cache(infile);
9739Sjkh			e = 1;
9749Sjkh			break;
9759Sjkh                }
9769Sjkh		break;
9779Sjkh	    }
9789Sjkh        }
9799Sjkh
9809Sjkh    keystring_eof:
9819Sjkh	*tp = 0;
9829Sjkh	aputs(keyval.string, out);
9839Sjkh    uncache_exit:
9849Sjkh	uncache(infile);
9859Sjkh	return r + e;
9869Sjkh}
9879Sjkh
9889Sjkh
98911894Speter	static void
99011894Speterescape_string(out, s)
99111894Speter	register FILE *out;
99211894Speter	register char const *s;
99311894Speter/* Output to OUT the string S, escaping chars that would break `ci -k'.  */
99411894Speter{
99511894Speter    register char c;
99611894Speter    for (;;)
99711894Speter	switch ((c = *s++)) {
99811894Speter	    case 0: return;
99911894Speter	    case '\t': aputs("\\t", out); break;
100011894Speter	    case '\n': aputs("\\n", out); break;
100111894Speter	    case ' ': aputs("\\040", out); break;
100211894Speter	    case KDELIM: aputs("\\044", out); break;
100311894Speter	    case '\\': if (VERSION(5)<=RCSversion) {aputs("\\\\", out); break;}
100411894Speter	    /* fall into */
100511894Speter	    default: aputc_(c, out) break;
100611894Speter	}
100711894Speter}
100811894Speter
10099Sjkhchar const ciklog[ciklogsize] = "checked in with -k by ";
10109Sjkh
10119Sjkh	static void
101211894Speterkeyreplace(marker, delta, delimstuffed, infile, out, dolog)
10139Sjkh	enum markers marker;
10149Sjkh	register struct hshentry const *delta;
101511894Speter	int delimstuffed;
101611894Speter	RILE *infile;
10179Sjkh	register FILE *out;
101811894Speter	int dolog;
10199Sjkh/* function: outputs the keyword value(s) corresponding to marker.
10209Sjkh * Attributes are derived from delta.
10219Sjkh */
10229Sjkh{
10239Sjkh	register char const *sp, *cp, *date;
102411894Speter	register int c;
10259Sjkh	register size_t cs, cw, ls;
10269Sjkh	char const *sp1;
102711894Speter	char datebuf[datesize + zonelenmax];
10289Sjkh	int RCSv;
102911894Speter	int exp;
10309Sjkh
10319Sjkh	sp = Keyword[(int)marker];
103211894Speter	exp = Expand;
103311894Speter	date = delta->date;
103411894Speter	RCSv = RCSversion;
10359Sjkh
103611894Speter	if (exp != VAL_EXPAND)
103711894Speter	    aprintf(out, "%c%s", KDELIM, sp);
103811894Speter	if (exp != KEY_EXPAND) {
10399Sjkh
104011894Speter	    if (exp != VAL_EXPAND)
104111894Speter		aprintf(out, "%c%c", VDELIM,
10429Sjkh			marker==Log && RCSv<VERSION(5)  ?  '\t'  :  ' '
10439Sjkh		);
10449Sjkh
104511894Speter	    switch (marker) {
104611894Speter	    case Author:
10479Sjkh		aputs(delta->author, out);
10489Sjkh                break;
104911894Speter	    case Date:
10509Sjkh		aputs(date2str(date,datebuf), out);
10519Sjkh                break;
105211894Speter	    case Id:
105311894Speter	    case Header:
105411894Speter		escape_string(out,
105511894Speter			marker==Id || RCSv<VERSION(4)
105611894Speter			? basefilename(RCSname)
105711894Speter			: getfullRCSname()
105811894Speter		);
105911894Speter		aprintf(out, " %s %s %s %s",
10609Sjkh			delta->num,
10619Sjkh			date2str(date, datebuf),
10629Sjkh			delta->author,
10639Sjkh			  RCSv==VERSION(3) && delta->lockedby ? "Locked"
10649Sjkh			: delta->state
10659Sjkh		);
106611894Speter		if (delta->lockedby)
10679Sjkh		    if (VERSION(5) <= RCSv) {
106811894Speter			if (locker_expansion || exp==KEYVALLOCK_EXPAND)
10699Sjkh			    aprintf(out, " %s", delta->lockedby);
10709Sjkh		    } else if (RCSv == VERSION(4))
10719Sjkh			aprintf(out, " Locker: %s", delta->lockedby);
10729Sjkh                break;
107311894Speter	    case Locker:
10749Sjkh		if (delta->lockedby)
10759Sjkh		    if (
10769Sjkh				locker_expansion
107711894Speter			||	exp == KEYVALLOCK_EXPAND
10789Sjkh			||	RCSv <= VERSION(4)
10799Sjkh		    )
10809Sjkh			aputs(delta->lockedby, out);
10819Sjkh                break;
108211894Speter	    case Log:
108311894Speter	    case RCSfile:
108411894Speter		escape_string(out, basefilename(RCSname));
10859Sjkh                break;
108611894Speter	    case Name:
108711894Speter		if (delta->name)
108811894Speter			aputs(delta->name, out);
108911894Speter		break;
109011894Speter	    case Revision:
10919Sjkh		aputs(delta->num, out);
10929Sjkh                break;
109311894Speter	    case Source:
109411894Speter		escape_string(out, getfullRCSname());
10959Sjkh                break;
109611894Speter	    case State:
10979Sjkh		aputs(delta->state, out);
10989Sjkh                break;
109911894Speter	    default:
11009Sjkh		break;
110111894Speter	    }
110211894Speter	    if (exp != VAL_EXPAND)
11039Sjkh		afputc(' ', out);
11049Sjkh	}
110511894Speter	if (exp != VAL_EXPAND)
110611894Speter	    afputc(KDELIM, out);
110711894Speter
110811894Speter	if (marker == Log   &&  dolog) {
110911894Speter		struct buf leader;
111011894Speter
11119Sjkh		sp = delta->log.string;
11129Sjkh		ls = delta->log.size;
11139Sjkh		if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
11149Sjkh			return;
111511894Speter		bufautobegin(&leader);
111611894Speter		if (RCSversion < VERSION(5)) {
111711894Speter		    cp = Comment.string;
111811894Speter		    cs = Comment.size;
111911894Speter		} else {
112011894Speter		    int kdelim_found = 0;
112111894Speter		    Ioffset_type chars_read = Itell(infile);
112211894Speter		    declarecache;
112311894Speter		    setupcache(infile); cache(infile);
112411894Speter
112511894Speter		    c = 0; /* Pacify `gcc -Wall'.  */
112611894Speter
112711894Speter		    /*
112811894Speter		    * Back up to the start of the current input line,
112911894Speter		    * setting CS to the number of characters before `$Log'.
113011894Speter		    */
113111894Speter		    cs = 0;
113211894Speter		    for (;;) {
113311894Speter			if (!--chars_read)
113411894Speter			    goto done_backing_up;
113511894Speter			cacheunget_(infile, c)
113611894Speter			if (c == '\n')
113711894Speter			    break;
113811894Speter			if (c == SDELIM  &&  delimstuffed) {
113911894Speter			    if (!--chars_read)
114011894Speter				break;
114111894Speter			    cacheunget_(infile, c)
114211894Speter			    if (c != SDELIM) {
114311894Speter				cacheget_(c)
114411894Speter				break;
114511894Speter			    }
114611894Speter			}
114711894Speter			cs += kdelim_found;
114811894Speter			kdelim_found |= c==KDELIM;
114911894Speter		    }
115011894Speter		    cacheget_(c)
115111894Speter		  done_backing_up:;
115211894Speter
115311894Speter		    /* Copy characters before `$Log' into LEADER.  */
115411894Speter		    bufalloc(&leader, cs);
115511894Speter		    cp = leader.string;
115611894Speter		    for (cw = 0;  cw < cs;  cw++) {
115711894Speter			leader.string[cw] = c;
115811894Speter			if (c == SDELIM  &&  delimstuffed)
115911894Speter			    cacheget_(c)
116011894Speter			cacheget_(c)
116111894Speter		    }
116211894Speter
116311894Speter		    /* Convert traditional C or Pascal leader to ` *'.  */
116411894Speter		    for (cw = 0;  cw < cs;  cw++)
116511894Speter			if (ctab[(unsigned char) cp[cw]] != SPACE)
116611894Speter			    break;
116711894Speter		    if (
116811894Speter			cw+1 < cs
116911894Speter			&&  cp[cw+1] == '*'
117011894Speter			&&  (cp[cw] == '/'  ||  cp[cw] == '(')
117111894Speter		    ) {
117211894Speter			size_t i = cw+1;
117311894Speter			for (;;)
117411894Speter			    if (++i == cs) {
117511894Speter				warn(
117611894Speter				    "`%c* $Log' is obsolescent; use ` * $Log'.",
117711894Speter				    cp[cw]
117811894Speter				);
117911894Speter				leader.string[cw] = ' ';
118011894Speter				break;
118111894Speter			    } else if (ctab[(unsigned char) cp[i]] != SPACE)
118211894Speter				break;
118311894Speter		    }
118411894Speter
118511894Speter		    /* Skip `$Log ... $' string.  */
118611894Speter		    do {
118711894Speter			cacheget_(c)
118811894Speter		    } while (c != KDELIM);
118911894Speter		    uncache(infile);
119011894Speter		}
11919Sjkh		afputc('\n', out);
11929Sjkh		awrite(cp, cs, out);
119311894Speter		sp1 = date2str(date, datebuf);
119411894Speter		if (VERSION(5) <= RCSv) {
119511894Speter		    aprintf(out, "Revision %s  %s  %s",
119611894Speter			delta->num, sp1, delta->author
119711894Speter		    );
119811894Speter		} else {
119911894Speter		    /* oddity: 2 spaces between date and time, not 1 as usual */
120011894Speter		    sp1 = strchr(sp1, ' ');
120111894Speter		    aprintf(out, "Revision %s  %.*s %s  %s",
120211894Speter			delta->num, (int)(sp1-datebuf), datebuf, sp1,
120311894Speter			delta->author
120411894Speter		    );
120511894Speter		}
12069Sjkh		/* Do not include state: it may change and is not updated.  */
120711894Speter		cw = cs;
12089Sjkh		if (VERSION(5) <= RCSv)
12099Sjkh		    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
121011894Speter			continue;
12119Sjkh		for (;;) {
12129Sjkh		    afputc('\n', out);
12139Sjkh		    awrite(cp, cw, out);
12149Sjkh		    if (!ls)
12159Sjkh			break;
12169Sjkh		    --ls;
12179Sjkh		    c = *sp++;
12189Sjkh		    if (c != '\n') {
12199Sjkh			awrite(cp+cw, cs-cw, out);
12209Sjkh			do {
12219Sjkh			    afputc(c,out);
12229Sjkh			    if (!ls)
12239Sjkh				break;
12249Sjkh			    --ls;
12259Sjkh			    c = *sp++;
12269Sjkh			} while (c != '\n');
12279Sjkh		    }
12289Sjkh		}
122911894Speter		bufautoend(&leader);
12309Sjkh	}
12319Sjkh}
12329Sjkh
12339Sjkh#if has_readlink
123411894Speter	static int resolve_symlink P((struct buf*));
12359Sjkh	static int
12369Sjkhresolve_symlink(L)
12379Sjkh	struct buf *L;
12389Sjkh/*
12399Sjkh * If L is a symbolic link, resolve it to the name that it points to.
12409Sjkh * If unsuccessful, set errno and yield -1.
12419Sjkh * If it points to an existing file, yield 1.
12429Sjkh * Otherwise, set errno=ENOENT and yield 0.
12439Sjkh */
12449Sjkh{
12459Sjkh	char *b, a[SIZEABLE_PATH];
12469Sjkh	int e;
12479Sjkh	size_t s;
12489Sjkh	ssize_t r;
12499Sjkh	struct buf bigbuf;
125011894Speter	int linkcount = MAXSYMLINKS;
12519Sjkh
12529Sjkh	b = a;
12539Sjkh	s = sizeof(a);
12549Sjkh	bufautobegin(&bigbuf);
12559Sjkh	while ((r = readlink(L->string,b,s))  !=  -1)
12569Sjkh	    if (r == s) {
12579Sjkh		bufalloc(&bigbuf, s<<1);
12589Sjkh		b = bigbuf.string;
12599Sjkh		s = bigbuf.size;
126011894Speter	    } else if (!linkcount--) {
126111894Speter#		ifndef ELOOP
126211894Speter		    /*
126311894Speter		    * Some pedantic Posix 1003.1-1990 hosts have readlink
126411894Speter		    * but not ELOOP.  Approximate ELOOP with EMLINK.
126511894Speter		    */
126611894Speter#		    define ELOOP EMLINK
126711894Speter#		endif
12689Sjkh		errno = ELOOP;
12699Sjkh		return -1;
12709Sjkh	    } else {
12719Sjkh		/* Splice symbolic link into L.  */
12729Sjkh		b[r] = '\0';
127311894Speter		L->string[
127411894Speter		  ROOTPATH(b)  ?  0  :  basefilename(L->string) - L->string
127511894Speter		] = '\0';
12769Sjkh		bufscat(L, b);
12779Sjkh	    }
12789Sjkh	e = errno;
12799Sjkh	bufautoend(&bigbuf);
12809Sjkh	errno = e;
12819Sjkh	switch (e) {
128211894Speter	    case readlink_isreg_errno: return 1;
12839Sjkh	    case ENOENT: return 0;
12849Sjkh	    default: return -1;
12859Sjkh	}
12869Sjkh}
12879Sjkh#endif
12889Sjkh
12899Sjkh	RILE *
12909Sjkhrcswriteopen(RCSbuf, status, mustread)
12919Sjkh	struct buf *RCSbuf;
12929Sjkh	struct stat *status;
12939Sjkh	int mustread;
12949Sjkh/*
129511894Speter * Create the lock file corresponding to RCSBUF.
129611894Speter * Then try to open RCSBUF for reading and yield its RILE* descriptor.
12979Sjkh * Put its status into *STATUS too.
12989Sjkh * MUSTREAD is true if the file must already exist, too.
12999Sjkh * If all goes well, discard any previously acquired locks,
130011894Speter * and set fdlock to the file descriptor of the RCS lockfile.
13019Sjkh */
13029Sjkh{
13039Sjkh	register char *tp;
130411894Speter	register char const *sp, *RCSpath, *x;
13059Sjkh	RILE *f;
13069Sjkh	size_t l;
130711894Speter	int e, exists, fdesc, fdescSafer, r, waslocked;
13089Sjkh	struct buf *dirt;
13099Sjkh	struct stat statbuf;
13109Sjkh
131111894Speter	waslocked  =  0 <= fdlock;
13129Sjkh	exists =
13139Sjkh#		if has_readlink
13149Sjkh			resolve_symlink(RCSbuf);
13159Sjkh#		else
13169Sjkh			    stat(RCSbuf->string, &statbuf) == 0  ?  1
13179Sjkh			:   errno==ENOENT ? 0 : -1;
13189Sjkh#		endif
131911894Speter	if (exists < (mustread|waslocked))
13209Sjkh		/*
13219Sjkh		 * There's an unusual problem with the RCS file;
13229Sjkh		 * or the RCS file doesn't exist,
13239Sjkh		 * and we must read or we already have a lock elsewhere.
13249Sjkh		 */
13259Sjkh		return 0;
13269Sjkh
132711894Speter	RCSpath = RCSbuf->string;
132811894Speter	sp = basefilename(RCSpath);
132911894Speter	l = sp - RCSpath;
133011894Speter	dirt = &dirtpname[waslocked];
133111894Speter	bufscpy(dirt, RCSpath);
13329Sjkh	tp = dirt->string + l;
133311894Speter	x = rcssuffix(RCSpath);
13349Sjkh#	if has_readlink
13359Sjkh	    if (!x) {
133611894Speter		error("symbolic link to non RCS file `%s'", RCSpath);
13379Sjkh		errno = EINVAL;
13389Sjkh		return 0;
13399Sjkh	    }
13409Sjkh#	endif
13419Sjkh	if (*sp == *x) {
134211894Speter		error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
13439Sjkh		errno = EINVAL;
13449Sjkh		return 0;
13459Sjkh	}
134611894Speter	/* Create a lock filename that is a function of the RCS filename.  */
13479Sjkh	if (*x) {
13489Sjkh		/*
13499Sjkh		 * The suffix is nonempty.
13509Sjkh		 * The lock filename is the first char of of the suffix,
13519Sjkh		 * followed by the RCS filename with last char removed.  E.g.:
13529Sjkh		 *	foo,v	RCS filename with suffix ,v
13539Sjkh		 *	,foo,	lock filename
13549Sjkh		 */
13559Sjkh		*tp++ = *x;
13569Sjkh		while (*sp)
13579Sjkh			*tp++ = *sp++;
13589Sjkh		*--tp = 0;
13599Sjkh	} else {
13609Sjkh		/*
13619Sjkh		 * The suffix is empty.
13629Sjkh		 * The lock filename is the RCS filename
13639Sjkh		 * with last char replaced by '_'.
13649Sjkh		 */
13659Sjkh		while ((*tp++ = *sp++))
136611894Speter			continue;
13679Sjkh		tp -= 2;
13689Sjkh		if (*tp == '_') {
136911894Speter			error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
13709Sjkh			errno = EINVAL;
13719Sjkh			return 0;
13729Sjkh		}
13739Sjkh		*tp = '_';
13749Sjkh	}
13759Sjkh
137611894Speter	sp = dirt->string;
13779Sjkh
13789Sjkh	f = 0;
13799Sjkh
13809Sjkh	/*
13819Sjkh	* good news:
138211894Speter	*	open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
138311894Speter	*	is atomic according to Posix 1003.1-1990.
13849Sjkh	* bad news:
13859Sjkh	*	NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
13869Sjkh	* good news:
138711894Speter	*	(O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity
138811894Speter	*	even with NFS.
13899Sjkh	* bad news:
139011894Speter	*	If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
139111894Speter	*	guarantee atomicity.
13929Sjkh	* good news:
13939Sjkh	*	Root-over-the-wire NFS access is rare for security reasons.
13949Sjkh	*	This bug has never been reported in practice with RCS.
13959Sjkh	* So we don't worry about this bug.
13969Sjkh	*
13979Sjkh	* An even rarer NFS bug can occur when clients retry requests.
139811894Speter	* This can happen in the usual case of NFS over UDP.
139911894Speter	* Suppose client A releases a lock by renaming ",f," to "f,v" at
140011894Speter	* about the same time that client B obtains a lock by creating ",f,",
14019Sjkh	* and suppose A's first rename request is delayed, so A reissues it.
14029Sjkh	* The sequence of events might be:
14039Sjkh	*	A sends rename(",f,", "f,v")
14049Sjkh	*	B sends create(",f,")
14059Sjkh	*	A sends retry of rename(",f,", "f,v")
14069Sjkh	*	server receives, does, and acknowledges A's first rename()
14079Sjkh	*	A receives acknowledgment, and its RCS program exits
14089Sjkh	*	server receives, does, and acknowledges B's create()
14099Sjkh	*	server receives, does, and acknowledges A's retry of rename()
14109Sjkh	* This not only wrongly deletes B's lock, it removes the RCS file!
14119Sjkh	* Most NFS implementations have idempotency caches that usually prevent
14129Sjkh	* this scenario, but such caches are finite and can be overrun.
141311894Speter	* This problem afflicts not only RCS, which uses open() and rename()
141411894Speter	* to get and release locks; it also afflicts the traditional
14159Sjkh	* Unix method of using link() and unlink() to get and release locks,
141611894Speter	* and the less traditional method of using mkdir() and rmdir().
141711894Speter	* There is no easy workaround.
14189Sjkh	* Any new method based on lockf() seemingly would be incompatible with
14199Sjkh	* the old methods; besides, lockf() is notoriously buggy under NFS.
14209Sjkh	* Since this problem afflicts scads of Unix programs, but is so rare
14219Sjkh	* that nobody seems to be worried about it, we won't worry either.
14229Sjkh	*/
14239Sjkh#	if !open_can_creat
142411894Speter#		define create(f) creat(f, OPEN_CREAT_READONLY)
14259Sjkh#	else
142611894Speter#		define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
14279Sjkh#	endif
14289Sjkh
14299Sjkh	catchints();
14309Sjkh	ignoreints();
14319Sjkh
14329Sjkh	/*
14339Sjkh	 * Create a lock file for an RCS file.  This should be atomic, i.e.
14349Sjkh	 * if two processes try it simultaneously, at most one should succeed.
14359Sjkh	 */
14369Sjkh	seteid();
14379Sjkh	fdesc = create(sp);
143811894Speter	fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr.  */
14399Sjkh	e = errno;
14409Sjkh	setrid();
14419Sjkh
144211894Speter	if (0 <= fdesc)
144311894Speter		dirtpmaker[0] = effective;
144411894Speter
144511894Speter	if (fdescSafer < 0) {
144611894Speter		if (e == EACCES  &&  stat(sp,&statbuf) == 0)
14479Sjkh			/* The RCS file is busy.  */
14489Sjkh			e = EEXIST;
14499Sjkh	} else {
14509Sjkh		e = ENOENT;
14519Sjkh		if (exists) {
145211894Speter		    f = Iopen(RCSpath, FOPEN_RB, status);
14539Sjkh		    e = errno;
145411894Speter		    if (f && waslocked) {
14559Sjkh			/* Discard the previous lock in favor of this one.  */
145611894Speter			ORCSclose();
14579Sjkh			seteid();
145811894Speter			r = un_link(lockname);
145911894Speter			e = errno;
14609Sjkh			setrid();
14619Sjkh			if (r != 0)
146211894Speter			    enfaterror(e, lockname);
146311894Speter			bufscpy(&dirtpname[lockdirtp_index], sp);
14649Sjkh		    }
14659Sjkh		}
146611894Speter		fdlock = fdescSafer;
14679Sjkh	}
14689Sjkh
14699Sjkh	restoreints();
14709Sjkh
14719Sjkh	errno = e;
14729Sjkh	return f;
14739Sjkh}
14749Sjkh
14759Sjkh	void
14769Sjkhkeepdirtemp(name)
14779Sjkh	char const *name;
14789Sjkh/* Do not unlink name, either because it's not there any more,
14799Sjkh * or because it has already been unlinked.
14809Sjkh */
14819Sjkh{
14829Sjkh	register int i;
14839Sjkh	for (i=DIRTEMPNAMES; 0<=--i; )
148411894Speter		if (dirtpname[i].string == name) {
148511894Speter			dirtpmaker[i] = notmade;
14869Sjkh			return;
14879Sjkh		}
14889Sjkh	faterror("keepdirtemp");
14899Sjkh}
14909Sjkh
14919Sjkh	char const *
149211894Spetermakedirtemp(isworkfile)
149311894Speter	int isworkfile;
14949Sjkh/*
149511894Speter * Create a unique pathname and store it into dirtpname.
149611894Speter * Because of storage in tpnames, dirtempunlink() can unlink the file later.
149711894Speter * Return a pointer to the pathname created.
149811894Speter * If ISWORKFILE is 1, put it into the working file's directory;
149911894Speter * if 0, put the unique file in RCSfile's directory.
15009Sjkh */
15019Sjkh{
15029Sjkh	register char *tp, *np;
15039Sjkh	register size_t dl;
15049Sjkh	register struct buf *bn;
150511894Speter	register char const *name = isworkfile ? workname : RCSname;
15069Sjkh
150711894Speter	dl = basefilename(name) - name;
150811894Speter	bn = &dirtpname[newRCSdirtp_index + isworkfile];
15099Sjkh	bufalloc(bn,
15109Sjkh#		if has_mktemp
15119Sjkh			dl + 9
15129Sjkh#		else
15139Sjkh			strlen(name) + 3
15149Sjkh#		endif
15159Sjkh	);
15169Sjkh	bufscpy(bn, name);
15179Sjkh	np = tp = bn->string;
15189Sjkh	tp += dl;
15199Sjkh	*tp++ = '_';
152011894Speter	*tp++ = '0'+isworkfile;
15219Sjkh	catchints();
15229Sjkh#	if has_mktemp
15239Sjkh		VOID strcpy(tp, "XXXXXX");
15249Sjkh		if (!mktemp(np) || !*np)
152511894Speter		    faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
152611894Speter			(int)dl, name, '0'+isworkfile
15279Sjkh		    );
15289Sjkh#	else
15299Sjkh		/*
15309Sjkh		 * Posix 1003.1-1990 has no reliable way
15319Sjkh		 * to create a unique file in a named directory.
153211894Speter		 * We fudge here.  If the filename is abcde,
15339Sjkh		 * the temp filename is _Ncde where N is a digit.
15349Sjkh		 */
15359Sjkh		name += dl;
15369Sjkh		if (*name) name++;
15379Sjkh		if (*name) name++;
15389Sjkh		VOID strcpy(tp, name);
15399Sjkh#	endif
154011894Speter	dirtpmaker[newRCSdirtp_index + isworkfile] = real;
15419Sjkh	return np;
15429Sjkh}
15439Sjkh
15449Sjkh	void
15459Sjkhdirtempunlink()
15469Sjkh/* Clean up makedirtemp() files.  May be invoked by signal handler. */
15479Sjkh{
15489Sjkh	register int i;
15499Sjkh	enum maker m;
15509Sjkh
15519Sjkh	for (i = DIRTEMPNAMES;  0 <= --i;  )
155211894Speter	    if ((m = dirtpmaker[i]) != notmade) {
15539Sjkh		if (m == effective)
15549Sjkh		    seteid();
155511894Speter		VOID un_link(dirtpname[i].string);
15569Sjkh		if (m == effective)
15579Sjkh		    setrid();
155811894Speter		dirtpmaker[i] = notmade;
15599Sjkh	    }
15609Sjkh}
15619Sjkh
15629Sjkh
15639Sjkh	int
15649Sjkh#if has_prototypes
156511894Speterchnamemod(
156611894Speter	FILE **fromp, char const *from, char const *to,
156711894Speter	int set_mode, mode_t mode, time_t mtime
156811894Speter)
15699Sjkh  /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
15709Sjkh#else
157111894Speter  chnamemod(fromp, from, to, set_mode, mode, mtime)
157211894Speter	FILE **fromp; char const *from,*to;
157311894Speter	int set_mode; mode_t mode; time_t mtime;
15749Sjkh#endif
15759Sjkh/*
157611894Speter * Rename a file (with stream pointer *FROMP) from FROM to TO.
15779Sjkh * FROM already exists.
157811894Speter * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
157911894Speter * If MTIME is not -1, change its mtime to MTIME before renaming.
158011894Speter * Close and clear *FROMP before renaming it.
15819Sjkh * Unlink TO if it already exists.
15829Sjkh * Return -1 on error (setting errno), 0 otherwise.
15839Sjkh */
15849Sjkh{
158511894Speter	mode_t mode_while_renaming = mode;
158611894Speter	int fchmod_set_mode = 0;
158711894Speter
158811894Speter#	if bad_a_rename || bad_NFS_rename
158911894Speter	    struct stat st;
159011894Speter	    if (bad_NFS_rename  ||  (bad_a_rename && set_mode <= 0)) {
159111894Speter		if (fstat(fileno(*fromp), &st) != 0)
159211894Speter		    return -1;
159311894Speter		if (bad_a_rename && set_mode <= 0)
159411894Speter		    mode = st.st_mode;
159511894Speter	    }
159611894Speter#	endif
159711894Speter
15989Sjkh#	if bad_a_rename
15999Sjkh		/*
160011894Speter		* There's a short window of inconsistency
160111894Speter		* during which the lock file is writable.
160211894Speter		*/
160311894Speter		mode_while_renaming = mode|S_IWUSR;
160411894Speter		if (mode != mode_while_renaming)
160511894Speter		    set_mode = 1;
16069Sjkh#	endif
160711894Speter
16089Sjkh#	if has_fchmod
160911894Speter	    if (0<set_mode  &&  fchmod(fileno(*fromp),mode_while_renaming) == 0)
161011894Speter		fchmod_set_mode = set_mode;
16119Sjkh#	endif
161211894Speter	/* If bad_chmod_close, we must close before chmod.  */
161311894Speter	Ozclose(fromp);
161411894Speter	if (fchmod_set_mode<set_mode  &&  chmod(from, mode_while_renaming) != 0)
161511894Speter	    return -1;
161611894Speter
161711894Speter	if (setmtime(from, mtime) != 0)
16189Sjkh		return -1;
16199Sjkh
16209Sjkh#	if !has_rename || bad_b_rename
16219Sjkh		/*
162211894Speter		* There's a short window of inconsistency
162311894Speter		* during which TO does not exist.
162411894Speter		*/
162511894Speter		if (un_link(to) != 0  &&  errno != ENOENT)
162611894Speter			return -1;
16279Sjkh#	endif
16289Sjkh
162911894Speter#	if has_rename
163011894Speter	    if (rename(from,to) != 0  &&  !(has_NFS && errno==ENOENT))
163111894Speter		return -1;
163211894Speter#	else
163311894Speter	    if (do_link(from,to) != 0  ||  un_link(from) != 0)
163411894Speter		return -1;
163511894Speter#	endif
16369Sjkh
163711894Speter#	if bad_NFS_rename
163811894Speter	{
163911894Speter	    /*
164011894Speter	    * Check whether the rename falsely reported success.
164111894Speter	    * A race condition can occur between the rename and the stat.
164211894Speter	    */
164311894Speter	    struct stat tostat;
164411894Speter	    if (stat(to, &tostat) != 0)
164511894Speter		return -1;
164611894Speter	    if (! same_file(st, tostat, 0)) {
164711894Speter		errno = EIO;
164811894Speter		return -1;
164911894Speter	    }
165011894Speter	}
165111894Speter#	endif
165211894Speter
165311894Speter#	if bad_a_rename
165411894Speter	    if (0 < set_mode  &&  chmod(to, mode) != 0)
165511894Speter		return -1;
165611894Speter#	endif
165711894Speter
165811894Speter	return 0;
16599Sjkh}
16609Sjkh
166111894Speter	int
166211894Spetersetmtime(file, mtime)
166311894Speter	char const *file;
166411894Speter	time_t mtime;
166511894Speter/* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1.  */
166611894Speter{
166711894Speter	static struct utimbuf amtime; /* static so unused fields are zero */
166811894Speter	if (mtime == -1)
166911894Speter		return 0;
167011894Speter	amtime.actime = now();
167111894Speter	amtime.modtime = mtime;
167211894Speter	return utime(file, &amtime);
167311894Speter}
16749Sjkh
16759Sjkh
167611894Speter
16779Sjkh	int
16789Sjkhfindlock(delete, target)
16799Sjkh	int delete;
16809Sjkh	struct hshentry **target;
16819Sjkh/*
16829Sjkh * Find the first lock held by caller and return a pointer
16839Sjkh * to the locked delta; also removes the lock if DELETE.
16849Sjkh * If one lock, put it into *TARGET.
16859Sjkh * Return 0 for no locks, 1 for one, 2 for two or more.
16869Sjkh */
16879Sjkh{
168811894Speter	register struct rcslock *next, **trail, **found;
16899Sjkh
16909Sjkh	found = 0;
16919Sjkh	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
16929Sjkh		if (strcmp(getcaller(), next->login)  ==  0) {
16939Sjkh			if (found) {
169411894Speter				rcserror("multiple revisions locked by %s; please specify one", getcaller());
16959Sjkh				return 2;
16969Sjkh			}
16979Sjkh			found = trail;
16989Sjkh		}
16999Sjkh	if (!found)
17009Sjkh		return 0;
17019Sjkh	next = *found;
17029Sjkh	*target = next->delta;
17039Sjkh	if (delete) {
170411894Speter		next->delta->lockedby = 0;
17059Sjkh		*found = next->nextlock;
17069Sjkh	}
17079Sjkh	return 1;
17089Sjkh}
17099Sjkh
17109Sjkh	int
171111894Speteraddlock(delta, verbose)
17129Sjkh	struct hshentry * delta;
171311894Speter	int verbose;
17149Sjkh/*
17159Sjkh * Add a lock held by caller to DELTA and yield 1 if successful.
171611894Speter * Print an error message if verbose and yield -1 if no lock is added because
17179Sjkh * DELTA is locked by somebody other than caller.
17189Sjkh * Return 0 if the caller already holds the lock.
17199Sjkh */
17209Sjkh{
172111894Speter	register struct rcslock *next;
17229Sjkh
17239Sjkh	for (next = Locks;  next;  next = next->nextlock)
17249Sjkh		if (cmpnum(delta->num, next->delta->num) == 0)
17259Sjkh			if (strcmp(getcaller(), next->login) == 0)
17269Sjkh				return 0;
17279Sjkh			else {
172811894Speter				if (verbose)
172911894Speter				  rcserror("Revision %s is already locked by %s.",
173011894Speter					delta->num, next->login
173111894Speter				  );
17329Sjkh				return -1;
17339Sjkh			}
173411894Speter	next = ftalloc(struct rcslock);
17359Sjkh	delta->lockedby = next->login = getcaller();
17369Sjkh	next->delta = delta;
17379Sjkh	next->nextlock = Locks;
17389Sjkh	Locks = next;
17399Sjkh	return 1;
17409Sjkh}
17419Sjkh
17429Sjkh
17439Sjkh	int
17449Sjkhaddsymbol(num, name, rebind)
17459Sjkh	char const *num, *name;
17469Sjkh	int rebind;
17479Sjkh/*
17489Sjkh * Associate with revision NUM the new symbolic NAME.
17499Sjkh * If NAME already exists and REBIND is set, associate NAME with NUM;
17509Sjkh * otherwise, print an error message and return false;
175111894Speter * Return -1 if unsuccessful, 0 if no change, 1 if change.
17529Sjkh */
17539Sjkh{
17549Sjkh	register struct assoc *next;
17559Sjkh
17569Sjkh	for (next = Symbols;  next;  next = next->nextassoc)
17579Sjkh		if (strcmp(name, next->symbol)  ==  0)
175811894Speter			if (strcmp(next->num,num) == 0)
175911894Speter				return 0;
176011894Speter			else if (rebind) {
17619Sjkh				next->num = num;
176211894Speter				return 1;
17639Sjkh			} else {
176411894Speter				rcserror("symbolic name %s already bound to %s",
17659Sjkh					name, next->num
17669Sjkh				);
176711894Speter				return -1;
17689Sjkh			}
17699Sjkh	next = ftalloc(struct assoc);
17709Sjkh	next->symbol = name;
17719Sjkh	next->num = num;
17729Sjkh	next->nextassoc = Symbols;
17739Sjkh	Symbols = next;
177411894Speter	return 1;
17759Sjkh}
17769Sjkh
17779Sjkh
17789Sjkh
17799Sjkh	char const *
17809Sjkhgetcaller()
17819Sjkh/* Get the caller's login name.  */
17829Sjkh{
17839Sjkh#	if has_setuid
17849Sjkh		return getusername(euid()!=ruid());
17859Sjkh#	else
17869Sjkh		return getusername(false);
17879Sjkh#	endif
17889Sjkh}
17899Sjkh
17909Sjkh
17919Sjkh	int
17929Sjkhcheckaccesslist()
17939Sjkh/*
17949Sjkh * Return true if caller is the superuser, the owner of the
17959Sjkh * file, the access list is empty, or caller is on the access list.
17969Sjkh * Otherwise, print an error message and return false.
17979Sjkh */
17989Sjkh{
17999Sjkh	register struct access const *next;
18009Sjkh
18019Sjkh	if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
18029Sjkh		return true;
18039Sjkh
18049Sjkh	next = AccessList;
18059Sjkh	do {
18069Sjkh		if (strcmp(getcaller(), next->login)  ==  0)
18079Sjkh			return true;
18089Sjkh	} while ((next = next->nextaccess));
18099Sjkh
181011894Speter	rcserror("user %s not on the access list", getcaller());
18119Sjkh	return false;
18129Sjkh}
18139Sjkh
18149Sjkh
18159Sjkh	int
18169Sjkhdorewrite(lockflag, changed)
18179Sjkh	int lockflag, changed;
18189Sjkh/*
18199Sjkh * Do nothing if LOCKFLAG is zero.
18209Sjkh * Prepare to rewrite an RCS file if CHANGED is positive.
18219Sjkh * Stop rewriting if CHANGED is zero, because there won't be any changes.
18229Sjkh * Fail if CHANGED is negative.
182311894Speter * Return 0 on success, -1 on failure.
18249Sjkh */
18259Sjkh{
182611894Speter	int r = 0, e;
18279Sjkh
18289Sjkh	if (lockflag)
18299Sjkh		if (changed) {
18309Sjkh			if (changed < 0)
183111894Speter				return -1;
183211894Speter			putadmin();
18339Sjkh			puttree(Head, frewrite);
18349Sjkh			aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
18359Sjkh			foutptr = frewrite;
18369Sjkh		} else {
183711894Speter#			if bad_creat0
183811894Speter				int nr = !!frewrite, ne = 0;
183911894Speter#			endif
184011894Speter			ORCSclose();
18419Sjkh			seteid();
18429Sjkh			ignoreints();
184311894Speter#			if bad_creat0
184411894Speter				if (nr) {
184511894Speter					nr = un_link(newRCSname);
184611894Speter					ne = errno;
184711894Speter					keepdirtemp(newRCSname);
184811894Speter				}
184911894Speter#			endif
185011894Speter			r = un_link(lockname);
18519Sjkh			e = errno;
185211894Speter			keepdirtemp(lockname);
18539Sjkh			restoreints();
18549Sjkh			setrid();
185511894Speter			if (r != 0)
185611894Speter				enerror(e, lockname);
185711894Speter#			if bad_creat0
185811894Speter				if (nr != 0) {
185911894Speter					enerror(ne, newRCSname);
186011894Speter					r = -1;
186111894Speter				}
186211894Speter#			endif
18639Sjkh		}
186411894Speter	return r;
18659Sjkh}
18669Sjkh
18679Sjkh	int
186811894Speterdonerewrite(changed, newRCStime)
18699Sjkh	int changed;
187011894Speter	time_t newRCStime;
18719Sjkh/*
18729Sjkh * Finish rewriting an RCS file if CHANGED is nonzero.
187311894Speter * Set its mode if CHANGED is positive.
187411894Speter * Set its modification time to NEWRCSTIME unless it is -1.
187511894Speter * Return 0 on success, -1 on failure.
18769Sjkh */
18779Sjkh{
187811894Speter	int r = 0, e = 0;
187911894Speter#	if bad_creat0
188011894Speter		int lr, le;
188111894Speter#	endif
18829Sjkh
18839Sjkh	if (changed && !nerror) {
18849Sjkh		if (finptr) {
18859Sjkh			fastcopy(finptr, frewrite);
18869Sjkh			Izclose(&finptr);
18879Sjkh		}
18889Sjkh		if (1 < RCSstat.st_nlink)
188911894Speter			rcswarn("breaking hard link");
189011894Speter		aflush(frewrite);
18919Sjkh		seteid();
18929Sjkh		ignoreints();
189311894Speter		r = chnamemod(
189411894Speter			&frewrite, newRCSname, RCSname, changed,
189511894Speter			RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
189611894Speter			newRCStime
18979Sjkh		);
18989Sjkh		e = errno;
189911894Speter		keepdirtemp(newRCSname);
190011894Speter#		if bad_creat0
190111894Speter			lr = un_link(lockname);
190211894Speter			le = errno;
190311894Speter			keepdirtemp(lockname);
190411894Speter#		endif
19059Sjkh		restoreints();
19069Sjkh		setrid();
19079Sjkh		if (r != 0) {
190811894Speter			enerror(e, RCSname);
190911894Speter			error("saved in %s", newRCSname);
19109Sjkh		}
191111894Speter#		if bad_creat0
191211894Speter			if (lr != 0) {
191311894Speter				enerror(le, lockname);
191411894Speter				r = -1;
191511894Speter			}
191611894Speter#		endif
19179Sjkh	}
191811894Speter	return r;
19199Sjkh}
19209Sjkh
19219Sjkh	void
192211894SpeterORCSclose()
19239Sjkh{
192411894Speter	if (0 <= fdlock) {
192511894Speter		if (close(fdlock) != 0)
192611894Speter			efaterror(lockname);
192711894Speter		fdlock = -1;
192811894Speter	}
192911894Speter	Ozclose(&frewrite);
19309Sjkh}
193111894Speter
193211894Speter	void
193311894SpeterORCSerror()
193411894Speter/*
193511894Speter* Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
193611894Speter* Do not report errors, since this may loop.  This is needed only because
193711894Speter* some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
193811894Speter* some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
193911894Speter* This isn't a completely reliable away to work around brain-damaged hosts,
194011894Speter* because of the gap between actual file opening and setting frewrite etc.,
194111894Speter* but it's better than nothing.
194211894Speter*/
194311894Speter{
194411894Speter	if (0 <= fdlock)
194511894Speter		VOID close(fdlock);
194611894Speter	if (frewrite)
194711894Speter		/* Avoid fclose, since stdio may not be reentrant.  */
194811894Speter		VOID close(fileno(frewrite));
194911894Speter}
1950