1275970Scy/* RCS stream editor */
2275970Scy
3275970Scy/******************************************************************************
4275970Scy *                       edits the input file according to a
5275970Scy *                       script from stdin, generated by diff -n
6289999Sglebius *                       performs keyword expansion
7289999Sglebius ******************************************************************************
8289999Sglebius */
9289999Sglebius
10289999Sglebius/* Copyright 1982, 1988, 1989 Walter Tichy
11289999Sglebius   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
12289999Sglebius   Distributed under license by the Free Software Foundation, Inc.
13289999Sglebius
14289999SglebiusThis file is part of RCS.
15289999Sglebius
16289999SglebiusRCS is free software; you can redistribute it and/or modify
17289999Sglebiusit under the terms of the GNU General Public License as published by
18289999Sglebiusthe Free Software Foundation; either version 2, or (at your option)
19289999Sglebiusany later version.
20289999Sglebius
21289999SglebiusRCS is distributed in the hope that it will be useful,
22289999Sglebiusbut WITHOUT ANY WARRANTY; without even the implied warranty of
23289999SglebiusMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24289999SglebiusGNU General Public License for more details.
25289999Sglebius
26289999SglebiusYou should have received a copy of the GNU General Public License
27289999Sglebiusalong with RCS; see the file COPYING.
28289999SglebiusIf not, write to the Free Software Foundation,
29289999Sglebius59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
30289999Sglebius
31289999SglebiusReport problems and direct all questions to:
32289999Sglebius
33289999Sglebius    rcs-bugs@cs.purdue.edu
34289999Sglebius
35289999Sglebius*/
36289999Sglebius
37289999Sglebius/*
38289999Sglebius * Revision 5.19  1995/06/16 06:19:24  eggert
39289999Sglebius * Update FSF address.
40289999Sglebius *
41289999Sglebius * Revision 5.18  1995/06/01 16:23:43  eggert
42289999Sglebius * (dirtpname): No longer external.
43289999Sglebius * (do_link): Simplify logic.
44289999Sglebius * (finisheditline, finishedit): Replace Iseek/Itell with what they stand for.
45289999Sglebius * (fopen_update_truncate): Replace `#if' with `if'.
46289999Sglebius * (keyreplace, makedirtemp): dirlen(x) -> basefilename(x)-x.
47289999Sglebius *
48289999Sglebius * (edit_string): Fix bug: if !large_memory, a bogus trailing `@' was output
49289999Sglebius * at the end of incomplete lines.
50289999Sglebius *
51289999Sglebius * (keyreplace): Do not assume that seeking backwards
52289999Sglebius * at the start of a file will fail; on some systems it succeeds.
53275970Scy * Convert C- and Pascal-style comment starts to ` *' in comment leader.
54289999Sglebius *
55275970Scy * (rcswriteopen): Use fdSafer to get safer file descriptor.
56275970Scy * Open RCS file with FOPEN_RB.
57275970Scy *
58275970Scy * (chnamemod): Work around bad_NFS_rename bug; don't ignore un_link result.
59275970Scy * Fall back on chmod if fchmod fails, since it might be ENOSYS.
60275970Scy *
61275970Scy * (aflush): Move to rcslex.c.
62275970Scy *
63275970Scy * Revision 5.17  1994/03/20 04:52:58  eggert
64289999Sglebius * Normally calculate the $Log prefix from context, not from RCS file.
65289999Sglebius * Move setmtime here from rcsutil.c.  Add ORCSerror.  Remove lint.
66289999Sglebius *
67289999Sglebius * Revision 5.16  1993/11/03 17:42:27  eggert
68289999Sglebius * Add -z.  Add Name keyword.  If bad_unlink, ignore errno when unlink fails.
69289999Sglebius * Escape white space, $, and \ in keyword string file names.
70289999Sglebius * Don't output 2 spaces between date and time after Log.
71289999Sglebius *
72289999Sglebius * Revision 5.15  1992/07/28  16:12:44  eggert
73289999Sglebius * Some hosts have readlink but not ELOOP.  Avoid `unsigned'.
74289999Sglebius * Preserve dates more systematically.  Statement macro names now end in _.
75289999Sglebius *
76289999Sglebius * Revision 5.14  1992/02/17  23:02:24  eggert
77289999Sglebius * Add -T support.
78289999Sglebius *
79289999Sglebius * Revision 5.13  1992/01/24  18:44:19  eggert
80289999Sglebius * Add support for bad_chmod_close, bad_creat0.
81289999Sglebius *
82289999Sglebius * Revision 5.12  1992/01/06  02:42:34  eggert
83289999Sglebius * Add setmode parameter to chnamemod.  addsymbol now reports changes.
84289999Sglebius * while (E) ; -> while (E) continue;
85289999Sglebius *
86289999Sglebius * Revision 5.11  1991/11/03  01:11:44  eggert
87289999Sglebius * Move the warning about link breaking to where they're actually being broken.
88289999Sglebius *
89289999Sglebius * Revision 5.10  1991/10/07  17:32:46  eggert
90289999Sglebius * Support piece tables even if !has_mmap.  Fix rare NFS bugs.
91275970Scy *
92275970Scy * Revision 5.9  1991/09/17  19:07:40  eggert
93275970Scy * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
94310419Sdelphij *
95275970Scy * Revision 5.8  1991/08/19  03:13:55  eggert
96275970Scy * Add piece tables, NFS bug workarounds.  Catch odd filenames.  Tune.
97275970Scy *
98275970Scy * Revision 5.7  1991/04/21  11:58:21  eggert
99275970Scy * Fix errno bugs.  Add -x, RCSINIT, MS-DOS support.
100275970Scy *
101275970Scy * Revision 5.6  1991/02/25  07:12:40  eggert
102275970Scy * Fix setuid bug.  Support new link behavior.  Work around broken "w+" fopen.
103275970Scy *
104275970Scy * Revision 5.5  1990/12/30  05:07:35  eggert
105275970Scy * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL).
106275970Scy *
107285612Sdelphij * Revision 5.4  1990/11/01  05:03:40  eggert
108275970Scy * Permit arbitrary data in comment leaders.
109275970Scy *
110275970Scy * Revision 5.3  1990/09/11  02:41:13  eggert
111275970Scy * Tune expandline().
112275970Scy *
113275970Scy * Revision 5.2  1990/09/04  08:02:21  eggert
114275970Scy * Count RCS lines better.  Improve incomplete line handling.
115275970Scy *
116275970Scy * Revision 5.1  1990/08/29  07:13:56  eggert
117275970Scy * Add -kkvl.
118275970Scy * Fix bug when getting revisions to files ending in incomplete lines.
119275970Scy * Fix bug in comment leader expansion.
120275970Scy *
121275970Scy * Revision 5.0  1990/08/22  08:12:47  eggert
122275970Scy * Don't require final newline.
123275970Scy * Don't append "checked in with -k by " to logs,
124275970Scy * so that checking in a program with -k doesn't change it.
125289999Sglebius * Don't generate trailing white space for empty comment leader.
126289999Sglebius * Remove compile-time limits; use malloc instead.  Add -k, -V.
127289999Sglebius * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
128289999Sglebius * Ansify and Posixate.  Check diff's output.
129289999Sglebius *
130289999Sglebius * Revision 4.8  89/05/01  15:12:35  narten
131289999Sglebius * changed copyright header to reflect current distribution rules
132289999Sglebius *
133289999Sglebius * Revision 4.7  88/11/08  13:54:14  narten
134289999Sglebius * misplaced semicolon caused infinite loop
135289999Sglebius *
136289999Sglebius * Revision 4.6  88/08/09  19:12:45  eggert
137289999Sglebius * Shrink stdio code size; allow cc -R.
138289999Sglebius *
139289999Sglebius * Revision 4.5  87/12/18  11:38:46  narten
140289999Sglebius * Changes from the 43. version. Don't know the significance of the
141289999Sglebius * first change involving "rewind". Also, additional "lint" cleanup.
142289999Sglebius * (Guy Harris)
143289999Sglebius *
144289999Sglebius * Revision 4.4  87/10/18  10:32:21  narten
145289999Sglebius * Updating version numbers. Changes relative to version 1.1 actually
146289999Sglebius * relative to 4.1
147289999Sglebius *
148289999Sglebius * Revision 1.4  87/09/24  13:59:29  narten
149289999Sglebius * Sources now pass through lint (if you ignore printf/sprintf/fprintf
150289999Sglebius * warnings)
151289999Sglebius *
152289999Sglebius * Revision 1.3  87/09/15  16:39:39  shepler
153289999Sglebius * added an initializatin of the variables editline and linecorr
154289999Sglebius * this will be done each time a file is processed.
155289999Sglebius * (there was an obscure bug where if co was used to retrieve multiple files
156289999Sglebius *  it would dump)
157289999Sglebius * fix attributed to  Roy Morris @FileNet Corp ...!felix!roy
158289999Sglebius *
159289999Sglebius * Revision 1.2  87/03/27  14:22:17  jenkins
160289999Sglebius * Port to suns
161289999Sglebius *
162289999Sglebius * Revision 4.1  83/05/12  13:10:30  wft
163289999Sglebius * Added new markers Id and RCSfile; added locker to Header and Id.
164289999Sglebius * Overhauled expandline completely() (problem with $01234567890123456789@).
165289999Sglebius * Moved trymatch() and marker table to rcskeys.c.
166289999Sglebius *
167289999Sglebius * Revision 3.7  83/05/12  13:04:39  wft
168289999Sglebius * Added retry to expandline to resume after failed match which ended in $.
169289999Sglebius * Fixed truncation problem for $19chars followed by@@.
170289999Sglebius * Log no longer expands full path of RCS file.
171289999Sglebius *
172289999Sglebius * Revision 3.6  83/05/11  16:06:30  wft
173289999Sglebius * added retry to expandline to resume after failed match which ended in $.
174289999Sglebius * Fixed truncation problem for $19chars followed by@@.
175289999Sglebius *
176289999Sglebius * Revision 3.5  82/12/04  13:20:56  wft
177289999Sglebius * Added expansion of keyword Locker.
178289999Sglebius *
179289999Sglebius * Revision 3.4  82/12/03  12:26:54  wft
180289999Sglebius * Added line number correction in case editing does not start at the
181289999Sglebius * beginning of the file.
182289999Sglebius * Changed keyword expansion to always print a space before closing KDELIM;
183289999Sglebius * Expansion for Header shortened.
184289999Sglebius *
185289999Sglebius * Revision 3.3  82/11/14  14:49:30  wft
186289999Sglebius * removed Suffix from keyword expansion. Replaced fclose with ffclose.
187289999Sglebius * keyreplace() gets log message from delta, not from curlogmsg.
188289999Sglebius * fixed expression overflow in while(c=putc(GETC....
189289999Sglebius * checked nil printing.
190289999Sglebius *
191289999Sglebius * Revision 3.2  82/10/18  21:13:39  wft
192289999Sglebius * I added checks for write errors during the co process, and renamed
193289999Sglebius * expandstring() to xpandstring().
194289999Sglebius *
195289999Sglebius * Revision 3.1  82/10/13  15:52:55  wft
196289999Sglebius * changed type of result of getc() from char to int.
197289999Sglebius * made keyword expansion loop in expandline() portable to machines
198289999Sglebius * without sign-extension.
199289999Sglebius */
200289999Sglebius
201289999Sglebius
202289999Sglebius#include "rcsbase.h"
203289999Sglebius
204289999SglebiuslibId(editId, "$FreeBSD$")
205289999Sglebius
206289999Sglebiusstatic void editEndsPrematurely P((void)) exiting;
207289999Sglebiusstatic void editLineNumberOverflow P((void)) exiting;
208289999Sglebiusstatic void escape_string P((FILE*,char const*));
209289999Sglebiusstatic void keyreplace P((enum markers,struct hshentry const*,int,RILE*,FILE*,int));
210289999Sglebius
211289999SglebiusFILE *fcopy;		 /* result file descriptor			    */
212289999Sglebiuschar const *resultname;	 /* result pathname				    */
213289999Sglebiusint locker_expansion;	 /* should the locker name be appended to Id val?   */
214289999Sglebius#if !large_memory
215289999Sglebius	static RILE *fedit; /* edit file descriptor */
216289999Sglebius	static char const *editname; /* edit pathname */
217289999Sglebius#endif
218289999Sglebiusstatic long editline; /* edit line counter; #lines before cursor   */
219289999Sglebiusstatic long linecorr; /* #adds - #deletes in each edit run.		    */
220289999Sglebius               /*used to correct editline in case file is not rewound after */
221289999Sglebius               /* applying one delta                                        */
222289999Sglebius
223289999Sglebius/* indexes into dirtpname */
224289999Sglebius#define lockdirtp_index 0
225289999Sglebius#define newRCSdirtp_index bad_creat0
226289999Sglebius#define newworkdirtp_index (newRCSdirtp_index+1)
227289999Sglebius#define DIRTEMPNAMES (newworkdirtp_index + 1)
228289999Sglebius
229289999Sglebiusenum maker {notmade, real, effective};
230289999Sglebiusstatic struct buf dirtpname[DIRTEMPNAMES];	/* unlink these when done */
231289999Sglebiusstatic enum maker volatile dirtpmaker[DIRTEMPNAMES];	/* if these are set */
232289999Sglebius#define lockname (dirtpname[lockdirtp_index].string)
233289999Sglebius#define newRCSname (dirtpname[newRCSdirtp_index].string)
234289999Sglebius
235289999Sglebius
236275970Scy#if has_NFS || bad_unlink
237275970Scy	int
238275970Scyun_link(s)
239275970Scy	char const *s;
240275970Scy/*
241275970Scy * Remove S, even if it is unwritable.
242275970Scy * Ignore unlink() ENOENT failures; NFS generates bogus ones.
243275970Scy */
244275970Scy{
245275970Scy#	if bad_unlink
246275970Scy		if (unlink(s) == 0)
247275970Scy			return 0;
248275970Scy		else {
249289999Sglebius			int e = errno;
250275970Scy			/*
251275970Scy			* Forge ahead even if errno == ENOENT; some completely
252275970Scy			* brain-damaged hosts (e.g. PCTCP 2.2) yield ENOENT
253275970Scy			* even for existing unwritable files.
254275970Scy			*/
255275970Scy			if (chmod(s, S_IWUSR) != 0) {
256275970Scy				errno = e;
257275970Scy				return -1;
258275970Scy			}
259289999Sglebius		}
260275970Scy#	endif
261275970Scy#	if has_NFS
262275970Scy		return unlink(s)==0 || errno==ENOENT  ?  0  :  -1;
263289999Sglebius#	else
264275970Scy		return unlink(s);
265275970Scy#	endif
266275970Scy}
267275970Scy#endif
268275970Scy
269275970Scy#if !has_rename
270275970Scy#  if !has_NFS
271275970Scy#	define do_link(s,t) link(s,t)
272275970Scy#  else
273275970Scy	static int do_link P((char const*,char const*));
274275970Scy	static int
275275970Scydo_link(s, t)
276275970Scy	char const *s, *t;
277275970Scy/* Link S to T, ignoring bogus EEXIST problems due to NFS failures.  */
278275970Scy{
279289999Sglebius	int r = link(s, t);
280275970Scy
281275970Scy	if (r != 0  &&  errno == EEXIST) {
282275970Scy		struct stat sb, tb;
283275970Scy		if (
284275970Scy		    stat(s, &sb) == 0  &&
285275970Scy		    stat(t, &tb) == 0  &&
286275970Scy		    same_file(sb, tb, 0)
287275970Scy		)
288275970Scy			r = 0;
289275970Scy		errno = EEXIST;
290275970Scy	}
291275970Scy	return r;
292289999Sglebius}
293275970Scy#  endif
294275970Scy#endif
295275970Scy
296289999Sglebius
297275970Scy	static void
298275970ScyeditEndsPrematurely()
299275970Scy{
300289999Sglebius	fatserror("edit script ends prematurely");
301275970Scy}
302275970Scy
303275970Scy	static void
304289999SglebiuseditLineNumberOverflow()
305275970Scy{
306275970Scy	fatserror("edit script refers to line past end of file");
307285612Sdelphij}
308275970Scy
309275970Scy
310275970Scy#if large_memory
311275970Scy
312275970Scy#if has_memmove
313275970Scy#	define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
314275970Scy#else
315275970Scy	static void movelines P((Iptr_type*,Iptr_type const*,long));
316275970Scy	static void
317275970Scymovelines(s1, s2, n)
318275970Scy	register Iptr_type *s1;
319275970Scy	register Iptr_type const *s2;
320275970Scy	register long n;
321275970Scy{
322275970Scy	if (s1 < s2)
323275970Scy		do {
324275970Scy			*s1++ = *s2++;
325275970Scy		} while (--n);
326275970Scy	else {
327275970Scy		s1 += n;
328275970Scy		s2 += n;
329275970Scy		do {
330275970Scy			*--s1 = *--s2;
331275970Scy		} while (--n);
332275970Scy	}
333275970Scy}
334275970Scy#endif
335275970Scy
336275970Scystatic void deletelines P((long,long));
337275970Scystatic void finisheditline P((RILE*,FILE*,Iptr_type,struct hshentry const*));
338275970Scystatic void insertline P((long,Iptr_type));
339275970Scystatic void snapshotline P((FILE*,Iptr_type));
340275970Scy
341275970Scy/*
342289999Sglebius * `line' contains pointers to the lines in the currently `edited' file.
343280849Scy * It is a 0-origin array that represents linelim-gapsize lines.
344289999Sglebius * line[0 .. gap-1] and line[gap+gapsize .. linelim-1] hold pointers to lines.
345275970Scy * line[gap .. gap+gapsize-1] contains garbage.
346289999Sglebius *
347275970Scy * Any @s in lines are duplicated.
348275970Scy * Lines are terminated by \n, or (for a last partial line only) by single @.
349275970Scy */
350275970Scystatic Iptr_type *line;
351275970Scystatic size_t gap, gapsize, linelim;
352275970Scy
353275970Scy	static void
354275970Scyinsertline(n, l)
355275970Scy	long n;
356289999Sglebius	Iptr_type l;
357275970Scy/* Before line N, insert line L.  N is 0-origin.  */
358289999Sglebius{
359285612Sdelphij	if (linelim-gapsize < n)
360275970Scy	    editLineNumberOverflow();
361275970Scy	if (!gapsize)
362275970Scy	    line =
363275970Scy		!linelim ?
364275970Scy			tnalloc(Iptr_type, linelim = gapsize = 1024)
365289999Sglebius		: (
366275970Scy			gap = gapsize = linelim,
367275970Scy			trealloc(Iptr_type, line, linelim <<= 1)
368275970Scy		);
369275970Scy	if (n < gap)
370275970Scy	    movelines(line+n+gapsize, line+n, gap-n);
371275970Scy	else if (gap < n)
372275970Scy	    movelines(line+gap, line+gap+gapsize, n-gap);
373275970Scy
374289999Sglebius	line[n] = l;
375275970Scy	gap = n + 1;
376275970Scy	gapsize--;
377275970Scy}
378275970Scy
379275970Scy	static void
380275970Scydeletelines(n, nlines)
381275970Scy	long n, nlines;
382275970Scy/* Delete lines N through N+NLINES-1.  N is 0-origin.  */
383275970Scy{
384275970Scy	long l = n + nlines;
385275970Scy	if (linelim-gapsize < l  ||  l < n)
386275970Scy	    editLineNumberOverflow();
387275970Scy	if (l < gap)
388275970Scy	    movelines(line+l+gapsize, line+l, gap-l);
389275970Scy	else if (gap < n)
390275970Scy	    movelines(line+gap, line+gap+gapsize, n-gap);
391275970Scy
392275970Scy	gap = n;
393275970Scy	gapsize += nlines;
394275970Scy}
395275970Scy
396275970Scy	static void
397275970Scysnapshotline(f, l)
398310419Sdelphij	register FILE *f;
399275970Scy	register Iptr_type l;
400275970Scy{
401275970Scy	register int c;
402275970Scy	do {
403275970Scy		if ((c = *l++) == SDELIM  &&  *l++ != SDELIM)
404275970Scy			return;
405275970Scy		aputc_(c, f)
406275970Scy	} while (c != '\n');
407275970Scy}
408275970Scy
409275970Scy	void
410275970Scysnapshotedit(f)
411275970Scy	FILE *f;
412275970Scy/* Copy the current state of the edits to F.  */
413275970Scy{
414275970Scy	register Iptr_type *p, *lim, *l=line;
415275970Scy	for (p=l, lim=l+gap;  p<lim;  )
416275970Scy		snapshotline(f, *p++);
417275970Scy	for (p+=gapsize, lim=l+linelim;  p<lim;  )
418275970Scy		snapshotline(f, *p++);
419275970Scy}
420275970Scy
421275970Scy	static void
422275970Scyfinisheditline(fin, fout, l, delta)
423275970Scy	RILE *fin;
424275970Scy	FILE *fout;
425275970Scy	Iptr_type l;
426275970Scy	struct hshentry const *delta;
427275970Scy{
428275970Scy	fin->ptr = l;
429275970Scy	if (expandline(fin, fout, delta, true, (FILE*)0, true)  <  0)
430275970Scy		faterror("finisheditline internal error");
431275970Scy}
432275970Scy
433275970Scy	void
434275970Scyfinishedit(delta, outfile, done)
435275970Scy	struct hshentry const *delta;
436275970Scy	FILE *outfile;
437275970Scy	int done;
438275970Scy/*
439275970Scy * Doing expansion if DELTA is set, output the state of the edits to OUTFILE.
440275970Scy * But do nothing unless DONE is set (which means we are on the last pass).
441275970Scy */
442275970Scy{
443275970Scy	if (done) {
444275970Scy		openfcopy(outfile);
445275970Scy		outfile = fcopy;
446310419Sdelphij		if (!delta)
447275970Scy			snapshotedit(outfile);
448275970Scy		else {
449275970Scy			register Iptr_type *p, *lim, *l = line;
450310419Sdelphij			register RILE *fin = finptr;
451275970Scy			Iptr_type here = fin->ptr;
452275970Scy			for (p=l, lim=l+gap;  p<lim;  )
453275970Scy				finisheditline(fin, outfile, *p++, delta);
454275970Scy			for (p+=gapsize, lim=l+linelim;  p<lim;  )
455275970Scy				finisheditline(fin, outfile, *p++, delta);
456275970Scy			fin->ptr = here;
457275970Scy		}
458275970Scy	}
459275970Scy}
460275970Scy
461275970Scy/* Open a temporary NAME for output, truncating any previous contents.  */
462275970Scy#   define fopen_update_truncate(name) fopenSafer(name, FOPEN_W_WORK)
463275970Scy#else /* !large_memory */
464275970Scy    static FILE * fopen_update_truncate P((char const*));
465275970Scy    static FILE *
466275970Scyfopen_update_truncate(name)
467275970Scy    char const *name;
468275970Scy{
469275970Scy	if (bad_fopen_wplus  &&  un_link(name) != 0)
470275970Scy		efaterror(name);
471275970Scy	return fopenSafer(name, FOPEN_WPLUS_WORK);
472275970Scy}
473275970Scy#endif
474275970Scy
475275970Scy
476275970Scy	void
477275970Scyopenfcopy(f)
478275970Scy	FILE *f;
479275970Scy{
480275970Scy	if (!(fcopy = f)) {
481275970Scy		if (!resultname)
482275970Scy			resultname = maketemp(2);
483275970Scy		if (!(fcopy = fopen_update_truncate(resultname)))
484275970Scy			efaterror(resultname);
485275970Scy	}
486275970Scy}
487275970Scy
488275970Scy
489275970Scy#if !large_memory
490275970Scy
491275970Scy	static void swapeditfiles P((FILE*));
492275970Scy	static void
493275970Scyswapeditfiles(outfile)
494275970Scy	FILE *outfile;
495275970Scy/* Function: swaps resultname and editname, assigns fedit=fcopy,
496275970Scy * and rewinds fedit for reading.  Set fcopy to outfile if nonnull;
497275970Scy * otherwise, set fcopy to be resultname opened for reading and writing.
498310419Sdelphij */
499275970Scy{
500275970Scy	char const *tmpptr;
501275970Scy
502275970Scy	editline = 0;  linecorr = 0;
503275970Scy	Orewind(fcopy);
504275970Scy	fedit = fcopy;
505275970Scy	tmpptr=editname; editname=resultname; resultname=tmpptr;
506275970Scy	openfcopy(outfile);
507275970Scy}
508275970Scy
509275970Scy	void
510275970Scysnapshotedit(f)
511285612Sdelphij	FILE *f;
512275970Scy/* Copy the current state of the edits to F.  */
513275970Scy{
514275970Scy	finishedit((struct hshentry *)0, (FILE*)0, false);
515275970Scy	fastcopy(fedit, f);
516275970Scy	Irewind(fedit);
517275970Scy}
518275970Scy
519275970Scy	void
520275970Scyfinishedit(delta, outfile, done)
521275970Scy	struct hshentry const *delta;
522275970Scy	FILE *outfile;
523289999Sglebius	int done;
524275970Scy/* copy the rest of the edit file and close it (if it exists).
525275970Scy * if delta, perform keyword substitution at the same time.
526289999Sglebius * If DONE is set, we are finishing the last pass.
527289999Sglebius */
528275970Scy{
529289999Sglebius	register RILE *fe;
530289999Sglebius	register FILE *fc;
531275970Scy
532275970Scy	fe = fedit;
533275970Scy	if (fe) {
534275970Scy		fc = fcopy;
535275970Scy		if (delta) {
536289999Sglebius			while (1 < expandline(fe,fc,delta,false,(FILE*)0,true))
537275970Scy				;
538275970Scy                } else {
539289999Sglebius			fastcopy(fe,fc);
540275970Scy                }
541275970Scy		Ifclose(fe);
542275970Scy        }
543275970Scy	if (!done)
544275970Scy		swapeditfiles(outfile);
545310419Sdelphij}
546310419Sdelphij#endif
547310419Sdelphij
548310419Sdelphij
549310419Sdelphij
550310419Sdelphij#if large_memory
551310419Sdelphij#	define copylines(upto,delta) (editline = (upto))
552310419Sdelphij#else
553310419Sdelphij	static void copylines P((long,struct hshentry const*));
554310419Sdelphij	static void
555310419Sdelphijcopylines(upto, delta)
556310419Sdelphij	register long upto;
557310419Sdelphij	struct hshentry const *delta;
558310419Sdelphij/*
559310419Sdelphij * Copy input lines editline+1..upto from fedit to fcopy.
560310419Sdelphij * If delta, keyword expansion is done simultaneously.
561310419Sdelphij * editline is updated. Rewinds a file only if necessary.
562310419Sdelphij */
563310419Sdelphij{
564310419Sdelphij	register int c;
565310419Sdelphij	declarecache;
566310419Sdelphij	register FILE *fc;
567310419Sdelphij	register RILE *fe;
568310419Sdelphij
569310419Sdelphij	if (upto < editline) {
570310419Sdelphij                /* swap files */
571310419Sdelphij		finishedit((struct hshentry *)0, (FILE*)0, false);
572275970Scy                /* assumes edit only during last pass, from the beginning*/
573310419Sdelphij        }
574275970Scy	fe = fedit;
575275970Scy	fc = fcopy;
576275970Scy	if (editline < upto)
577275970Scy	    if (delta)
578275970Scy		do {
579275970Scy		    if (expandline(fe,fc,delta,false,(FILE*)0,true) <= 1)
580275970Scy			editLineNumberOverflow();
581275970Scy		} while (++editline < upto);
582275970Scy	    else {
583310419Sdelphij		setupcache(fe); cache(fe);
584275970Scy		do {
585275970Scy			do {
586275970Scy				cachegeteof_(c, editLineNumberOverflow();)
587275970Scy				aputc_(c, fc)
588275970Scy			} while (c != '\n');
589275970Scy		} while (++editline < upto);
590275970Scy		uncache(fe);
591275970Scy	    }
592275970Scy}
593289999Sglebius#endif
594275970Scy
595285612Sdelphij
596275970Scy
597285612Sdelphij	void
598310419Sdelphijxpandstring(delta)
599275970Scy	struct hshentry const *delta;
600275970Scy/* Function: Reads a string terminated by SDELIM from finptr and writes it
601275970Scy * to fcopy. Double SDELIM is replaced with single SDELIM.
602275970Scy * Keyword expansion is performed with data from delta.
603289999Sglebius * If foutptr is nonnull, the string is also copied unchanged to foutptr.
604285612Sdelphij */
605275970Scy{
606285612Sdelphij	while (1 < expandline(finptr,fcopy,delta,true,foutptr,true))
607285612Sdelphij		continue;
608275970Scy}
609285612Sdelphij
610275970Scy
611310419Sdelphij	void
612275970Scycopystring()
613275970Scy/* Function: copies a string terminated with a single SDELIM from finptr to
614275970Scy * fcopy, replacing all double SDELIM with a single SDELIM.
615275970Scy * If foutptr is nonnull, the string also copied unchanged to foutptr.
616289999Sglebius * editline is incremented by the number of lines copied.
617275970Scy * Assumption: next character read is first string character.
618275970Scy */
619275970Scy{	register c;
620275970Scy	declarecache;
621275970Scy	register FILE *frew, *fcop;
622310419Sdelphij	register int amidline;
623275970Scy	register RILE *fin;
624275970Scy
625275970Scy	fin = finptr;
626275970Scy	setupcache(fin); cache(fin);
627275970Scy	frew = foutptr;
628275970Scy	fcop = fcopy;
629275970Scy	amidline = false;
630275970Scy	for (;;) {
631275970Scy		GETC_(frew,c)
632310419Sdelphij		switch (c) {
633275970Scy		    case '\n':
634275970Scy			++editline;
635275970Scy			++rcsline;
636275970Scy			amidline = false;
637275970Scy			break;
638275970Scy		    case SDELIM:
639275970Scy			GETC_(frew,c)
640275970Scy			if (c != SDELIM) {
641275970Scy				/* end of string */
642289999Sglebius				nextc = c;
643275970Scy				editline += amidline;
644275970Scy				uncache(fin);
645275970Scy				return;
646275970Scy			}
647310419Sdelphij			/* fall into */
648275970Scy		    default:
649275970Scy			amidline = true;
650275970Scy			break;
651275970Scy                }
652289999Sglebius		aputc_(c,fcop)
653285612Sdelphij        }
654275970Scy}
655285612Sdelphij
656275970Scy
657275970Scy	void
658275970Scyenterstring()
659275970Scy/* Like copystring, except the string is put into the edit data structure.  */
660275970Scy{
661275970Scy#if !large_memory
662275970Scy	editname = 0;
663275970Scy	fedit = 0;
664275970Scy	editline = linecorr = 0;
665289999Sglebius	resultname = maketemp(1);
666275970Scy	if (!(fcopy = fopen_update_truncate(resultname)))
667275970Scy		efaterror(resultname);
668275970Scy	copystring();
669275970Scy#else
670275970Scy	register int c;
671275970Scy	declarecache;
672310419Sdelphij	register FILE *frew;
673275970Scy	register long e, oe;
674275970Scy	register int amidline, oamidline;
675275970Scy	register Iptr_type optr;
676310419Sdelphij	register RILE *fin;
677275970Scy
678275970Scy	e = 0;
679275970Scy	gap = 0;
680310419Sdelphij	gapsize = linelim;
681275970Scy	fin = finptr;
682275970Scy	setupcache(fin); cache(fin);
683275970Scy	advise_access(fin, MADV_NORMAL);
684275970Scy	frew = foutptr;
685310419Sdelphij	amidline = false;
686275970Scy	for (;;) {
687275970Scy		optr = cacheptr();
688275970Scy		GETC_(frew,c)
689275970Scy		oamidline = amidline;
690275970Scy		oe = e;
691275970Scy		switch (c) {
692275970Scy		    case '\n':
693289999Sglebius			++e;
694275970Scy			++rcsline;
695289999Sglebius			amidline = false;
696289999Sglebius			break;
697289999Sglebius		    case SDELIM:
698289999Sglebius			GETC_(frew,c)
699289999Sglebius			if (c != SDELIM) {
700289999Sglebius				/* end of string */
701289999Sglebius				nextc = c;
702289999Sglebius				editline = e + amidline;
703289999Sglebius				linecorr = 0;
704289999Sglebius				uncache(fin);
705289999Sglebius				return;
706275970Scy			}
707289999Sglebius			/* fall into */
708275970Scy		    default:
709289999Sglebius			amidline = true;
710275970Scy			break;
711289999Sglebius		}
712289999Sglebius		if (!oamidline)
713275970Scy			insertline(oe, optr);
714289999Sglebius	}
715289999Sglebius#endif
716289999Sglebius}
717275970Scy
718289999Sglebius
719289999Sglebius
720289999Sglebius
721289999Sglebius	void
722289999Sglebius#if large_memory
723289999Sglebiusedit_string()
724289999Sglebius#else
725289999Sglebius  editstring(delta)
726289999Sglebius	struct hshentry const *delta;
727289999Sglebius#endif
728289999Sglebius/*
729289999Sglebius * Read an edit script from finptr and applies it to the edit file.
730289999Sglebius#if !large_memory
731285612Sdelphij * The result is written to fcopy.
732289999Sglebius * If delta, keyword expansion is performed simultaneously.
733289999Sglebius * If running out of lines in fedit, fedit and fcopy are swapped.
734289999Sglebius * editname is the name of the file that goes with fedit.
735289999Sglebius#endif
736289999Sglebius * If foutptr is set, the edit script is also copied verbatim to foutptr.
737289999Sglebius * Assumes that all these files are open.
738289999Sglebius * resultname is the name of the file that goes with fcopy.
739289999Sglebius * Assumes the next input character from finptr is the first character of
740289999Sglebius * the edit script. Resets nextc on exit.
741275970Scy */
742289999Sglebius{
743289999Sglebius        int ed; /* editor command */
744275970Scy        register int c;
745289999Sglebius	declarecache;
746289999Sglebius	register FILE *frew;
747275970Scy#	if !large_memory
748289999Sglebius		register FILE *f;
749289999Sglebius		long line_lim = LONG_MAX;
750289999Sglebius		register RILE *fe;
751275970Scy#	endif
752289999Sglebius	register long i;
753289999Sglebius	register RILE *fin;
754289999Sglebius#	if large_memory
755289999Sglebius		register long j;
756289999Sglebius#	endif
757289999Sglebius	struct diffcmd dc;
758289999Sglebius
759289999Sglebius        editline += linecorr; linecorr=0; /*correct line number*/
760289999Sglebius	frew = foutptr;
761275970Scy	fin = finptr;
762275970Scy	setupcache(fin);
763275970Scy	initdiffcmd(&dc);
764275970Scy	while (0  <=  (ed = getdiffcmd(fin,true,frew,&dc)))
765275970Scy#if !large_memory
766310419Sdelphij		if (line_lim <= dc.line1)
767275970Scy			editLineNumberOverflow();
768275970Scy		else
769275970Scy#endif
770310419Sdelphij		if (!ed) {
771275970Scy			copylines(dc.line1-1, delta);
772275970Scy                        /* skip over unwanted lines */
773275970Scy			i = dc.nlines;
774275970Scy			linecorr -= i;
775275970Scy			editline += i;
776275970Scy#			if large_memory
777275970Scy			    deletelines(editline+linecorr, i);
778289999Sglebius#			else
779289999Sglebius			    fe = fedit;
780289999Sglebius			    do {
781289999Sglebius                                /*skip next line*/
782289999Sglebius				do {
783275970Scy				    Igeteof_(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } )
784289999Sglebius				} while (c != '\n');
785289999Sglebius			    } while (--i);
786275970Scy#			endif
787289999Sglebius		} else {
788289999Sglebius			/* Copy lines without deleting any.  */
789289999Sglebius			copylines(dc.line1, delta);
790275970Scy			i = dc.nlines;
791289999Sglebius#			if large_memory
792289999Sglebius				j = editline+linecorr;
793289999Sglebius#			endif
794289999Sglebius			linecorr += i;
795289999Sglebius#if !large_memory
796289999Sglebius			f = fcopy;
797289999Sglebius			if (delta)
798289999Sglebius			    do {
799289999Sglebius				switch (expandline(fin,f,delta,true,frew,true)){
800275970Scy				    case 0: case 1:
801275970Scy					if (i==1)
802275970Scy					    return;
803310419Sdelphij					/* fall into */
804275970Scy				    case -1:
805275970Scy					editEndsPrematurely();
806275970Scy				}
807275970Scy			    } while (--i);
808275970Scy			else
809275970Scy#endif
810275970Scy			{
811275970Scy			    cache(fin);
812275970Scy			    do {
813275970Scy#				if large_memory
814275970Scy				    insertline(j++, cacheptr());
815275970Scy#				endif
816275970Scy				for (;;) {
817275970Scy				    GETC_(frew, c)
818289999Sglebius				    if (c==SDELIM) {
819289999Sglebius					GETC_(frew, c)
820289999Sglebius					if (c!=SDELIM) {
821289999Sglebius					    if (--i)
822289999Sglebius						editEndsPrematurely();
823289999Sglebius					    nextc = c;
824289999Sglebius					    uncache(fin);
825275970Scy					    return;
826289999Sglebius					}
827289999Sglebius				    }
828285612Sdelphij#				    if !large_memory
829289999Sglebius					aputc_(c, f)
830289999Sglebius#				    endif
831289999Sglebius				    if (c == '\n')
832289999Sglebius					break;
833289999Sglebius				}
834289999Sglebius				++rcsline;
835289999Sglebius			    } while (--i);
836289999Sglebius			    uncache(fin);
837289999Sglebius			}
838289999Sglebius                }
839289999Sglebius}
840289999Sglebius
841289999Sglebius
842285612Sdelphij
843289999Sglebius/* The rest is for keyword expansion */
844289999Sglebius
845289999Sglebius
846289999Sglebius
847289999Sglebius	int
848289999Sglebiusexpandline(infile, outfile, delta, delimstuffed, frewfile, dolog)
849289999Sglebius	RILE *infile;
850289999Sglebius	FILE *outfile, *frewfile;
851289999Sglebius	struct hshentry const *delta;
852275970Scy	int delimstuffed, dolog;
853289999Sglebius/*
854289999Sglebius * Read a line from INFILE and write it to OUTFILE.
855289999Sglebius * Do keyword expansion with data from DELTA.
856289999Sglebius * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
857275970Scy * If FREWFILE is set, copy the line unchanged to FREWFILE.
858275970Scy * DELIMSTUFFED must be true if FREWFILE is set.
859275970Scy * Append revision history to log only if DOLOG is set.
860275970Scy * Yields -1 if no data is copied, 0 if an incomplete line is copied,
861275970Scy * 2 if a complete line is copied; adds 1 to yield if expansion occurred.
862275970Scy */
863275970Scy{
864275970Scy	register c;
865275970Scy	declarecache;
866275970Scy	register FILE *out, *frew;
867275970Scy	register char * tp;
868275970Scy	register int e, ds, r;
869275970Scy	char const *tlim;
870275970Scy	static struct buf keyval;
871275970Scy        enum markers matchresult;
872275970Scy
873275970Scy	setupcache(infile); cache(infile);
874275970Scy	out = outfile;
875275970Scy	frew = frewfile;
876275970Scy	ds = delimstuffed;
877275970Scy	bufalloc(&keyval, keylength+3);
878275970Scy	e = 0;
879275970Scy	r = -1;
880275970Scy
881275970Scy        for (;;) {
882275970Scy	    if (ds)
883275970Scy		GETC_(frew, c)
884275970Scy	    else
885275970Scy		cachegeteof_(c, goto uncache_exit;)
886275970Scy	    for (;;) {
887275970Scy		switch (c) {
888275970Scy		    case SDELIM:
889275970Scy			if (ds) {
890275970Scy			    GETC_(frew, c)
891275970Scy			    if (c != SDELIM) {
892275970Scy                                /* end of string */
893275970Scy                                nextc=c;
894275970Scy				goto uncache_exit;
895275970Scy			    }
896275970Scy			}
897275970Scy			/* fall into */
898275970Scy		    default:
899275970Scy			aputc_(c,out)
900275970Scy			r = 0;
901275970Scy			break;
902275970Scy
903275970Scy		    case '\n':
904275970Scy			rcsline += ds;
905275970Scy			aputc_(c,out)
906289999Sglebius			r = 2;
907289999Sglebius			goto uncache_exit;
908275970Scy
909285612Sdelphij		    case KDELIM:
910285612Sdelphij			r = 0;
911285612Sdelphij                        /* check for keyword */
912285612Sdelphij                        /* first, copy a long enough string into keystring */
913285612Sdelphij			tp = keyval.string;
914285612Sdelphij			*tp++ = KDELIM;
915285612Sdelphij			for (;;) {
916289999Sglebius			    if (ds)
917289999Sglebius				GETC_(frew, c)
918289999Sglebius			    else
919275970Scy				cachegeteof_(c, goto keystring_eof;)
920289999Sglebius			    if (tp <= &keyval.string[keylength])
921289999Sglebius				switch (ctab[c]) {
922289999Sglebius				    case LETTER: case Letter:
923289999Sglebius					*tp++ = c;
924289999Sglebius					continue;
925289999Sglebius				    default:
926289999Sglebius					break;
927289999Sglebius				}
928289999Sglebius			    break;
929289999Sglebius                        }
930275970Scy			*tp++ = c; *tp = '\0';
931275970Scy			matchresult = trymatch(keyval.string+1);
932275970Scy			if (matchresult==Nomatch) {
933289999Sglebius				tp[-1] = 0;
934275970Scy				aputs(keyval.string, out);
935275970Scy				continue;   /* last c handled properly */
936275970Scy			}
937289999Sglebius
938275970Scy			/* Now we have a keyword terminated with a K/VDELIM */
939275970Scy			if (c==VDELIM) {
940275970Scy			      /* try to find closing KDELIM, and replace value */
941275970Scy			      tlim = keyval.string + keyval.size;
942275970Scy			      for (;;) {
943275970Scy				      if (ds)
944275970Scy					GETC_(frew, c)
945275970Scy				      else
946275970Scy					cachegeteof_(c, goto keystring_eof;)
947275970Scy				      if (c=='\n' || c==KDELIM)
948275970Scy					break;
949275970Scy				      *tp++ =c;
950275970Scy				      if (tlim <= tp)
951275970Scy					  tp = bufenlarge(&keyval, &tlim);
952289999Sglebius				      if (c==SDELIM && ds) { /*skip next SDELIM */
953275970Scy						GETC_(frew, c)
954275970Scy						if (c != SDELIM) {
955289999Sglebius							/* end of string before closing KDELIM or newline */
956275970Scy							nextc = c;
957289999Sglebius							goto keystring_eof;
958275970Scy						}
959275970Scy				      }
960289999Sglebius			      }
961275970Scy			      if (c!=KDELIM) {
962275970Scy				    /* couldn't find closing KDELIM -- give up */
963275970Scy				    *tp = 0;
964275970Scy				    aputs(keyval.string, out);
965289999Sglebius				    continue;   /* last c handled properly */
966275970Scy			      }
967275970Scy			}
968275970Scy			/* now put out the new keyword value */
969289999Sglebius			uncache(infile);
970275970Scy			keyreplace(matchresult, delta, ds, infile, out, dolog);
971275970Scy			cache(infile);
972275970Scy			e = 1;
973275970Scy			break;
974275970Scy                }
975275970Scy		break;
976275970Scy	    }
977275970Scy        }
978275970Scy
979275970Scy    keystring_eof:
980275970Scy	*tp = 0;
981275970Scy	aputs(keyval.string, out);
982275970Scy    uncache_exit:
983275970Scy	uncache(infile);
984275970Scy	return r + e;
985275970Scy}
986285612Sdelphij
987275970Scy
988275970Scy	static void
989275970Scyescape_string(out, s)
990275970Scy	register FILE *out;
991275970Scy	register char const *s;
992275970Scy/* Output to OUT the string S, escaping chars that would break `ci -k'.  */
993275970Scy{
994275970Scy    register char c;
995275970Scy    for (;;)
996275970Scy	switch ((c = *s++)) {
997275970Scy	    case 0: return;
998275970Scy	    case '\t': aputs("\\t", out); break;
999275970Scy	    case '\n': aputs("\\n", out); break;
1000275970Scy	    case ' ': aputs("\\040", out); break;
1001275970Scy	    case KDELIM: aputs("\\044", out); break;
1002275970Scy	    case '\\': if (VERSION(5)<=RCSversion) {aputs("\\\\", out); break;}
1003275970Scy	    /* fall into */
1004275970Scy	    default: aputc_(c, out) break;
1005275970Scy	}
1006275970Scy}
1007275970Scy
1008275970Scychar const ciklog[ciklogsize] = "checked in with -k by ";
1009285612Sdelphij
1010275970Scy	static void
1011275970Scykeyreplace(marker, delta, delimstuffed, infile, out, dolog)
1012275970Scy	enum markers marker;
1013275970Scy	register struct hshentry const *delta;
1014275970Scy	int delimstuffed;
1015275970Scy	RILE *infile;
1016275970Scy	register FILE *out;
1017275970Scy	int dolog;
1018275970Scy/* function: outputs the keyword value(s) corresponding to marker.
1019275970Scy * Attributes are derived from delta.
1020275970Scy */
1021275970Scy{
1022275970Scy	register char const *sp, *cp, *date;
1023275970Scy	register int c;
1024275970Scy	register size_t cs, cw, ls;
1025275970Scy	char const *sp1;
1026275970Scy	char datebuf[datesize + zonelenmax];
1027275970Scy	int RCSv;
1028275970Scy	int exp;
1029275970Scy
1030275970Scy	sp = Keyword[(int)marker];
1031275970Scy	exp = Expand;
1032275970Scy	date = delta->date;
1033275970Scy	RCSv = RCSversion;
1034275970Scy
1035275970Scy	if (exp != VAL_EXPAND)
1036275970Scy	    aprintf(out, "%c%s", KDELIM, sp);
1037275970Scy	if (exp != KEY_EXPAND) {
1038275970Scy
1039275970Scy	    if (exp != VAL_EXPAND)
1040275970Scy		aprintf(out, "%c%c", VDELIM,
1041275970Scy			marker==Log && RCSv<VERSION(5)  ?  '\t'  :  ' '
1042275970Scy		);
1043275970Scy
1044275970Scy	    switch (marker) {
1045275970Scy	    case Author:
1046275970Scy		aputs(delta->author, out);
1047275970Scy                break;
1048275970Scy	    case Date:
1049275970Scy		aputs(date2str(date,datebuf), out);
1050275970Scy                break;
1051275970Scy	    case Id:
1052275970Scy	    case LocalId:
1053275970Scy	    case Header:
1054275970Scy	    case CVSHeader:
1055275970Scy		if (marker == Id || RCSv < VERSION(4) ||
1056275970Scy		    (marker == LocalId && LocalIdMode == Id))
1057275970Scy			escape_string(out, basefilename(RCSname));
1058275970Scy		else if (marker == CVSHeader ||
1059275970Scy		    (marker == LocalId && LocalIdMode == CVSHeader))
1060275970Scy			escape_string(out, getfullCVSname());
1061275970Scy		else
1062275970Scy			escape_string(out, getfullRCSname());
1063275970Scy		aprintf(out, " %s %s %s %s",
1064275970Scy			delta->num,
1065275970Scy			date2str(date, datebuf),
1066275970Scy			delta->author,
1067275970Scy			  RCSv==VERSION(3) && delta->lockedby ? "Locked"
1068275970Scy			: delta->state
1069275970Scy		);
1070275970Scy		if (delta->lockedby)
1071275970Scy		    if (VERSION(5) <= RCSv) {
1072275970Scy			if (locker_expansion || exp==KEYVALLOCK_EXPAND)
1073275970Scy			    aprintf(out, " %s", delta->lockedby);
1074275970Scy		    } else if (RCSv == VERSION(4))
1075275970Scy			aprintf(out, " Locker: %s", delta->lockedby);
1076275970Scy                break;
1077275970Scy	    case Locker:
1078275970Scy		if (delta->lockedby)
1079275970Scy		    if (
1080275970Scy				locker_expansion
1081275970Scy			||	exp == KEYVALLOCK_EXPAND
1082275970Scy			||	RCSv <= VERSION(4)
1083310419Sdelphij		    )
1084275970Scy			aputs(delta->lockedby, out);
1085275970Scy                break;
1086275970Scy	    case Log:
1087310419Sdelphij	    case RCSfile:
1088275970Scy		escape_string(out, basefilename(RCSname));
1089275970Scy                break;
1090275970Scy	    case Name:
1091275970Scy		if (delta->name)
1092275970Scy			aputs(delta->name, out);
1093275970Scy		break;
1094275970Scy	    case Revision:
1095275970Scy		aputs(delta->num, out);
1096275970Scy                break;
1097275970Scy	    case Source:
1098275970Scy		escape_string(out, getfullRCSname());
1099275970Scy                break;
1100275970Scy	    case State:
1101275970Scy		aputs(delta->state, out);
1102275970Scy                break;
1103275970Scy	    default:
1104289999Sglebius		break;
1105275970Scy	    }
1106275970Scy	    if (exp != VAL_EXPAND)
1107275970Scy		afputc(' ', out);
1108275970Scy	}
1109275970Scy	if (exp != VAL_EXPAND)
1110289999Sglebius	    afputc(KDELIM, out);
1111275970Scy
1112275970Scy	if (marker == Log   &&  dolog) {
1113275970Scy		struct buf leader;
1114275970Scy
1115275970Scy		sp = delta->log.string;
1116275970Scy		ls = delta->log.size;
1117275970Scy		if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
1118275970Scy			return;
1119275970Scy		bufautobegin(&leader);
1120275970Scy		if (RCSversion < VERSION(5)) {
1121275970Scy		    cp = Comment.string;
1122275970Scy		    cs = Comment.size;
1123275970Scy		} else {
1124275970Scy		    int kdelim_found = 0;
1125275970Scy		    Ioffset_type chars_read = Itell(infile);
1126275970Scy		    declarecache;
1127275970Scy		    setupcache(infile); cache(infile);
1128275970Scy
1129275970Scy		    c = 0; /* Pacify `gcc -Wall'.  */
1130275970Scy
1131275970Scy		    /*
1132275970Scy		    * Back up to the start of the current input line,
1133275970Scy		    * setting CS to the number of characters before `$Log'.
1134275970Scy		    */
1135275970Scy		    cs = 0;
1136275970Scy		    for (;;) {
1137275970Scy			if (!--chars_read)
1138285612Sdelphij			    goto done_backing_up;
1139275970Scy			cacheunget_(infile, c)
1140275970Scy			if (c == '\n')
1141275970Scy			    break;
1142275970Scy			if (c == SDELIM  &&  delimstuffed) {
1143275970Scy			    if (!--chars_read)
1144275970Scy				break;
1145275970Scy			    cacheunget_(infile, c)
1146275970Scy			    if (c != SDELIM) {
1147275970Scy				cacheget_(c)
1148275970Scy				break;
1149289999Sglebius			    }
1150275970Scy			}
1151275970Scy			cs += kdelim_found;
1152275970Scy			kdelim_found |= c==KDELIM;
1153275970Scy		    }
1154275970Scy		    cacheget_(c)
1155275970Scy		  done_backing_up:;
1156289999Sglebius
1157275970Scy		    /* Copy characters before `$Log' into LEADER.  */
1158275970Scy		    bufalloc(&leader, cs);
1159275970Scy		    cp = leader.string;
1160289999Sglebius		    for (cw = 0;  cw < cs;  cw++) {
1161275970Scy			leader.string[cw] = c;
1162275970Scy			if (c == SDELIM  &&  delimstuffed)
1163275970Scy			    cacheget_(c)
1164289999Sglebius			cacheget_(c)
1165289999Sglebius		    }
1166289999Sglebius
1167289999Sglebius		    /* Convert traditional C or Pascal leader to ` *'.  */
1168289999Sglebius		    for (cw = 0;  cw < cs;  cw++)
1169275970Scy			if (ctab[(unsigned char) cp[cw]] != SPACE)
1170289999Sglebius			    break;
1171275970Scy		    if (
1172289999Sglebius			cw+1 < cs
1173289999Sglebius			&&  cp[cw+1] == '*'
1174289999Sglebius			&&  (cp[cw] == '/'  ||  cp[cw] == '(')
1175289999Sglebius		    ) {
1176289999Sglebius			size_t i = cw+1;
1177289999Sglebius			for (;;)
1178289999Sglebius			    if (++i == cs) {
1179289999Sglebius				warn(
1180289999Sglebius				    "`%c* $Log' is obsolescent; use ` * $Log'.",
1181289999Sglebius				    cp[cw]
1182289999Sglebius				);
1183289999Sglebius				leader.string[cw] = ' ';
1184289999Sglebius				break;
1185275970Scy			    } else if (ctab[(unsigned char) cp[i]] != SPACE)
1186289999Sglebius				break;
1187275970Scy		    }
1188275970Scy
1189275970Scy		    /* Skip `$Log ... $' string.  */
1190275970Scy		    do {
1191289999Sglebius			cacheget_(c)
1192289999Sglebius		    } while (c != KDELIM);
1193289999Sglebius		    uncache(infile);
1194289999Sglebius		}
1195289999Sglebius		afputc('\n', out);
1196289999Sglebius		awrite(cp, cs, out);
1197289999Sglebius		sp1 = date2str(date, datebuf);
1198289999Sglebius		if (VERSION(5) <= RCSv) {
1199289999Sglebius		    aprintf(out, "Revision %s  %s  %s",
1200289999Sglebius			delta->num, sp1, delta->author
1201289999Sglebius		    );
1202289999Sglebius		} else {
1203289999Sglebius		    /* oddity: 2 spaces between date and time, not 1 as usual */
1204275970Scy		    sp1 = strchr(sp1, ' ');
1205275970Scy		    aprintf(out, "Revision %s  %.*s %s  %s",
1206275970Scy			delta->num, (int)(sp1-datebuf), datebuf, sp1,
1207275970Scy			delta->author
1208275970Scy		    );
1209275970Scy		}
1210275970Scy		/* Do not include state: it may change and is not updated.  */
1211275970Scy		cw = cs;
1212275970Scy		if (VERSION(5) <= RCSv)
1213275970Scy		    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
1214275970Scy			continue;
1215275970Scy		for (;;) {
1216275970Scy		    afputc('\n', out);
1217275970Scy		    awrite(cp, cw, out);
1218275970Scy		    if (!ls)
1219275970Scy			break;
1220275970Scy		    --ls;
1221275970Scy		    c = *sp++;
1222275970Scy		    if (c != '\n') {
1223275970Scy			awrite(cp+cw, cs-cw, out);
1224275970Scy			do {
1225289999Sglebius			    afputc(c,out);
1226289999Sglebius			    if (!ls)
1227289999Sglebius				break;
1228289999Sglebius			    --ls;
1229289999Sglebius			    c = *sp++;
1230289999Sglebius			} while (c != '\n');
1231289999Sglebius		    }
1232275970Scy		}
1233289999Sglebius		bufautoend(&leader);
1234289999Sglebius	}
1235289999Sglebius}
1236289999Sglebius
1237289999Sglebius#if has_readlink
1238289999Sglebius	static int resolve_symlink P((struct buf*));
1239275970Scy	static int
1240289999Sglebiusresolve_symlink(L)
1241275970Scy	struct buf *L;
1242275970Scy/*
1243275970Scy * If L is a symbolic link, resolve it to the name that it points to.
1244275970Scy * If unsuccessful, set errno and yield -1.
1245275970Scy * If it points to an existing file, yield 1.
1246275970Scy * Otherwise, set errno=ENOENT and yield 0.
1247275970Scy */
1248275970Scy{
1249275970Scy	char *b, a[SIZEABLE_PATH];
1250275970Scy	int e;
1251275970Scy	size_t s;
1252275970Scy	ssize_t r;
1253275970Scy	struct buf bigbuf;
1254275970Scy	int linkcount = MAXSYMLINKS;
1255275970Scy
1256275970Scy	b = a;
1257275970Scy	s = sizeof(a);
1258275970Scy	bufautobegin(&bigbuf);
1259275970Scy	while ((r = readlink(L->string,b,s))  !=  -1)
1260275970Scy	    if (r == s) {
1261275970Scy		bufalloc(&bigbuf, s<<1);
1262275970Scy		b = bigbuf.string;
1263275970Scy		s = bigbuf.size;
1264275970Scy	    } else if (!linkcount--) {
1265275970Scy#		ifndef ELOOP
1266275970Scy		    /*
1267275970Scy		    * Some pedantic Posix 1003.1-1990 hosts have readlink
1268275970Scy		    * but not ELOOP.  Approximate ELOOP with EMLINK.
1269275970Scy		    */
1270275970Scy#		    define ELOOP EMLINK
1271275970Scy#		endif
1272275970Scy		errno = ELOOP;
1273275970Scy		return -1;
1274275970Scy	    } else {
1275275970Scy		/* Splice symbolic link into L.  */
1276275970Scy		b[r] = '\0';
1277275970Scy		L->string[
1278275970Scy		  ROOTPATH(b)  ?  0  :  basefilename(L->string) - L->string
1279275970Scy		] = '\0';
1280275970Scy		bufscat(L, b);
1281310419Sdelphij	    }
1282310419Sdelphij	e = errno;
1283275970Scy	bufautoend(&bigbuf);
1284275970Scy	errno = e;
1285275970Scy	switch (e) {
1286275970Scy	    case readlink_isreg_errno: return 1;
1287275970Scy	    case ENOENT: return 0;
1288275970Scy	    default: return -1;
1289275970Scy	}
1290275970Scy}
1291275970Scy#endif
1292275970Scy
1293275970Scy	RILE *
1294275970Scyrcswriteopen(RCSbuf, status, mustread)
1295275970Scy	struct buf *RCSbuf;
1296275970Scy	struct stat *status;
1297275970Scy	int mustread;
1298275970Scy/*
1299275970Scy * Create the lock file corresponding to RCSBUF.
1300275970Scy * Then try to open RCSBUF for reading and yield its RILE* descriptor.
1301275970Scy * Put its status into *STATUS too.
1302275970Scy * MUSTREAD is true if the file must already exist, too.
1303275970Scy * If all goes well, discard any previously acquired locks,
1304275970Scy * and set fdlock to the file descriptor of the RCS lockfile.
1305275970Scy */
1306275970Scy{
1307275970Scy	register char *tp;
1308275970Scy	register char const *sp, *RCSpath, *x;
1309275970Scy	RILE *f;
1310275970Scy	size_t l;
1311275970Scy	int e, exists, fdesc, fdescSafer, r, waslocked;
1312275970Scy	struct buf *dirt;
1313275970Scy	struct stat statbuf;
1314275970Scy
1315275970Scy	waslocked  =  0 <= fdlock;
1316275970Scy	exists =
1317275970Scy#		if has_readlink
1318275970Scy			resolve_symlink(RCSbuf);
1319275970Scy#		else
1320275970Scy			    stat(RCSbuf->string, &statbuf) == 0  ?  1
1321275970Scy			:   errno==ENOENT ? 0 : -1;
1322275970Scy#		endif
1323275970Scy	if (exists < (mustread|waslocked))
1324275970Scy		/*
1325275970Scy		 * There's an unusual problem with the RCS file;
1326275970Scy		 * or the RCS file doesn't exist,
1327275970Scy		 * and we must read or we already have a lock elsewhere.
1328275970Scy		 */
1329275970Scy		return 0;
1330275970Scy
1331275970Scy	RCSpath = RCSbuf->string;
1332275970Scy	sp = basefilename(RCSpath);
1333275970Scy	l = sp - RCSpath;
1334275970Scy	dirt = &dirtpname[waslocked];
1335275970Scy	bufscpy(dirt, RCSpath);
1336275970Scy	tp = dirt->string + l;
1337275970Scy	x = rcssuffix(RCSpath);
1338275970Scy#	if has_readlink
1339275970Scy	    if (!x) {
1340275970Scy		error("symbolic link to non RCS file `%s'", RCSpath);
1341275970Scy		errno = EINVAL;
1342275970Scy		return 0;
1343275970Scy	    }
1344275970Scy#	endif
1345275970Scy	if (*sp == *x) {
1346275970Scy		error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
1347275970Scy		errno = EINVAL;
1348275970Scy		return 0;
1349275970Scy	}
1350275970Scy	/* Create a lock filename that is a function of the RCS filename.  */
1351275970Scy	if (*x) {
1352275970Scy		/*
1353275970Scy		 * The suffix is nonempty.
1354275970Scy		 * The lock filename is the first char of of the suffix,
1355275970Scy		 * followed by the RCS filename with last char removed.  E.g.:
1356275970Scy		 *	foo,v	RCS filename with suffix ,v
1357275970Scy		 *	,foo,	lock filename
1358275970Scy		 */
1359275970Scy		*tp++ = *x;
1360275970Scy		while (*sp)
1361275970Scy			*tp++ = *sp++;
1362275970Scy		*--tp = 0;
1363275970Scy	} else {
1364275970Scy		/*
1365275970Scy		 * The suffix is empty.
1366275970Scy		 * The lock filename is the RCS filename
1367275970Scy		 * with last char replaced by '_'.
1368275970Scy		 */
1369275970Scy		while ((*tp++ = *sp++))
1370275970Scy			continue;
1371275970Scy		tp -= 2;
1372275970Scy		if (*tp == '_') {
1373275970Scy			error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
1374275970Scy			errno = EINVAL;
1375275970Scy			return 0;
1376275970Scy		}
1377275970Scy		*tp = '_';
1378275970Scy	}
1379275970Scy
1380275970Scy	sp = dirt->string;
1381275970Scy
1382275970Scy	f = 0;
1383275970Scy
1384275970Scy	/*
1385275970Scy	* good news:
1386275970Scy	*	open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
1387275970Scy	*	is atomic according to Posix 1003.1-1990.
1388275970Scy	* bad news:
1389275970Scy	*	NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
1390275970Scy	* good news:
1391275970Scy	*	(O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity
1392275970Scy	*	even with NFS.
1393275970Scy	* bad news:
1394275970Scy	*	If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
1395275970Scy	*	guarantee atomicity.
1396275970Scy	* good news:
1397275970Scy	*	Root-over-the-wire NFS access is rare for security reasons.
1398275970Scy	*	This bug has never been reported in practice with RCS.
1399275970Scy	* So we don't worry about this bug.
1400275970Scy	*
1401275970Scy	* An even rarer NFS bug can occur when clients retry requests.
1402275970Scy	* This can happen in the usual case of NFS over UDP.
1403275970Scy	* Suppose client A releases a lock by renaming ",f," to "f,v" at
1404275970Scy	* about the same time that client B obtains a lock by creating ",f,",
1405275970Scy	* and suppose A's first rename request is delayed, so A reissues it.
1406275970Scy	* The sequence of events might be:
1407275970Scy	*	A sends rename(",f,", "f,v")
1408275970Scy	*	B sends create(",f,")
1409275970Scy	*	A sends retry of rename(",f,", "f,v")
1410275970Scy	*	server receives, does, and acknowledges A's first rename()
1411275970Scy	*	A receives acknowledgment, and its RCS program exits
1412275970Scy	*	server receives, does, and acknowledges B's create()
1413275970Scy	*	server receives, does, and acknowledges A's retry of rename()
1414275970Scy	* This not only wrongly deletes B's lock, it removes the RCS file!
1415275970Scy	* Most NFS implementations have idempotency caches that usually prevent
1416275970Scy	* this scenario, but such caches are finite and can be overrun.
1417275970Scy	* This problem afflicts not only RCS, which uses open() and rename()
1418275970Scy	* to get and release locks; it also afflicts the traditional
1419275970Scy	* Unix method of using link() and unlink() to get and release locks,
1420275970Scy	* and the less traditional method of using mkdir() and rmdir().
1421275970Scy	* There is no easy workaround.
1422275970Scy	* Any new method based on lockf() seemingly would be incompatible with
1423275970Scy	* the old methods; besides, lockf() is notoriously buggy under NFS.
1424275970Scy	* Since this problem afflicts scads of Unix programs, but is so rare
1425275970Scy	* that nobody seems to be worried about it, we won't worry either.
1426275970Scy	*/
1427275970Scy#	if !open_can_creat
1428275970Scy#		define create(f) creat(f, OPEN_CREAT_READONLY)
1429275970Scy#	else
1430275970Scy#		define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
1431275970Scy#	endif
1432275970Scy
1433275970Scy	catchints();
1434275970Scy	ignoreints();
1435275970Scy
1436275970Scy	/*
1437275970Scy	 * Create a lock file for an RCS file.  This should be atomic, i.e.
1438275970Scy	 * if two processes try it simultaneously, at most one should succeed.
1439275970Scy	 */
1440275970Scy	seteid();
1441275970Scy	fdesc = create(sp);
1442275970Scy	fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr.  */
1443275970Scy	e = errno;
1444275970Scy	setrid();
1445275970Scy
1446275970Scy	if (0 <= fdesc)
1447275970Scy		dirtpmaker[0] = effective;
1448275970Scy
1449275970Scy	if (fdescSafer < 0) {
1450275970Scy		if (e == EACCES  &&  stat(sp,&statbuf) == 0)
1451275970Scy			/* The RCS file is busy.  */
1452275970Scy			e = EEXIST;
1453275970Scy	} else {
1454275970Scy		e = ENOENT;
1455275970Scy		if (exists) {
1456275970Scy		    f = Iopen(RCSpath, FOPEN_RB, status);
1457275970Scy		    e = errno;
1458275970Scy		    if (f && waslocked) {
1459275970Scy			/* Discard the previous lock in favor of this one.  */
1460275970Scy			ORCSclose();
1461275970Scy			seteid();
1462275970Scy			r = un_link(lockname);
1463275970Scy			e = errno;
1464310419Sdelphij			setrid();
1465275970Scy			if (r != 0)
1466275970Scy			    enfaterror(e, lockname);
1467275970Scy			bufscpy(&dirtpname[lockdirtp_index], sp);
1468310419Sdelphij		    }
1469275970Scy		}
1470275970Scy		fdlock = fdescSafer;
1471275970Scy	}
1472275970Scy
1473275970Scy	restoreints();
1474275970Scy
1475275970Scy	errno = e;
1476275970Scy	return f;
1477285612Sdelphij}
1478275970Scy
1479275970Scy	void
1480275970Scykeepdirtemp(name)
1481275970Scy	char const *name;
1482275970Scy/* Do not unlink name, either because it's not there any more,
1483275970Scy * or because it has already been unlinked.
1484275970Scy */
1485275970Scy{
1486275970Scy	register int i;
1487275970Scy	for (i=DIRTEMPNAMES; 0<=--i; )
1488275970Scy		if (dirtpname[i].string == name) {
1489275970Scy			dirtpmaker[i] = notmade;
1490275970Scy			return;
1491275970Scy		}
1492285612Sdelphij	faterror("keepdirtemp");
1493275970Scy}
1494275970Scy
1495275970Scy	char const *
1496275970Scymakedirtemp(isworkfile)
1497275970Scy	int isworkfile;
1498275970Scy/*
1499275970Scy * Create a unique pathname and store it into dirtpname.
1500275970Scy * Because of storage in tpnames, dirtempunlink() can unlink the file later.
1501275970Scy * Return a pointer to the pathname created.
1502275970Scy * If ISWORKFILE is 1, put it into the working file's directory;
1503275970Scy * if 0, put the unique file in RCSfile's directory.
1504275970Scy */
1505275970Scy{
1506275970Scy	register char *tp, *np;
1507275970Scy	register size_t dl;
1508275970Scy	register struct buf *bn;
1509275970Scy	register char const *name = isworkfile ? workname : RCSname;
1510275970Scy#	if has_mktemp
1511275970Scy	int fd;
1512275970Scy#	endif
1513275970Scy
1514275970Scy	dl = basefilename(name) - name;
1515275970Scy	bn = &dirtpname[newRCSdirtp_index + isworkfile];
1516275970Scy	bufalloc(bn,
1517275970Scy#		if has_mktemp
1518275970Scy			dl + 9
1519275970Scy#		else
1520275970Scy			strlen(name) + 3
1521275970Scy#		endif
1522275970Scy	);
1523275970Scy	bufscpy(bn, name);
1524275970Scy	np = tp = bn->string;
1525275970Scy	tp += dl;
1526275970Scy	*tp++ = '_';
1527275970Scy	*tp++ = '0'+isworkfile;
1528275970Scy	catchints();
1529275970Scy#	if has_mktemp
1530310419Sdelphij		VOID strcpy(tp, "XXXXXX");
1531275970Scy		fd = mkstemp(np);
1532275970Scy		if (fd < 0 || !*np)
1533275970Scy		    faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
1534310419Sdelphij			(int)dl, name, '0'+isworkfile
1535275970Scy		    );
1536275970Scy		close(fd);
1537275970Scy#	else
1538275970Scy		/*
1539275970Scy		 * Posix 1003.1-1990 has no reliable way
1540275970Scy		 * to create a unique file in a named directory.
1541275970Scy		 * We fudge here.  If the filename is abcde,
1542275970Scy		 * the temp filename is _Ncde where N is a digit.
1543275970Scy		 */
1544275970Scy		name += dl;
1545275970Scy		if (*name) name++;
1546275970Scy		if (*name) name++;
1547275970Scy		VOID strcpy(tp, name);
1548275970Scy#	endif
1549275970Scy	dirtpmaker[newRCSdirtp_index + isworkfile] = real;
1550275970Scy	return np;
1551275970Scy}
1552275970Scy
1553275970Scy	void
1554275970Scydirtempunlink()
1555275970Scy/* Clean up makedirtemp() files.  May be invoked by signal handler. */
1556275970Scy{
1557275970Scy	register int i;
1558275970Scy	enum maker m;
1559275970Scy
1560275970Scy	for (i = DIRTEMPNAMES;  0 <= --i;  )
1561275970Scy	    if ((m = dirtpmaker[i]) != notmade) {
1562275970Scy		if (m == effective)
1563275970Scy		    seteid();
1564275970Scy		VOID un_link(dirtpname[i].string);
1565275970Scy		if (m == effective)
1566275970Scy		    setrid();
1567275970Scy		dirtpmaker[i] = notmade;
1568275970Scy	    }
1569275970Scy}
1570275970Scy
1571275970Scy
1572275970Scy	int
1573275970Scy#if has_prototypes
1574275970Scychnamemod(
1575275970Scy	FILE **fromp, char const *from, char const *to,
1576275970Scy	int set_mode, mode_t mode, time_t mtime
1577275970Scy)
1578275970Scy  /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1579275970Scy#else
1580275970Scy  chnamemod(fromp, from, to, set_mode, mode, mtime)
1581275970Scy	FILE **fromp; char const *from,*to;
1582275970Scy	int set_mode; mode_t mode; time_t mtime;
1583275970Scy#endif
1584275970Scy/*
1585275970Scy * Rename a file (with stream pointer *FROMP) from FROM to TO.
1586275970Scy * FROM already exists.
1587310419Sdelphij * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
1588275970Scy * If MTIME is not -1, change its mtime to MTIME before renaming.
1589275970Scy * Close and clear *FROMP before renaming it.
1590275970Scy * Unlink TO if it already exists.
1591275970Scy * Return -1 on error (setting errno), 0 otherwise.
1592275970Scy */
1593275970Scy{
1594275970Scy	mode_t mode_while_renaming = mode;
1595275970Scy	int fchmod_set_mode = 0;
1596275970Scy
1597275970Scy#	if bad_a_rename || bad_NFS_rename
1598275970Scy	    struct stat st;
1599275970Scy	    if (bad_NFS_rename  ||  (bad_a_rename && set_mode <= 0)) {
1600275970Scy		if (fstat(fileno(*fromp), &st) != 0)
1601275970Scy		    return -1;
1602275970Scy		if (bad_a_rename && set_mode <= 0)
1603275970Scy		    mode = st.st_mode;
1604275970Scy	    }
1605275970Scy#	endif
1606275970Scy
1607275970Scy#	if bad_a_rename
1608275970Scy		/*
1609275970Scy		* There's a short window of inconsistency
1610275970Scy		* during which the lock file is writable.
1611275970Scy		*/
1612275970Scy		mode_while_renaming = mode|S_IWUSR;
1613275970Scy		if (mode != mode_while_renaming)
1614275970Scy		    set_mode = 1;
1615275970Scy#	endif
1616275970Scy
1617275970Scy#	if has_fchmod
1618275970Scy	    if (0<set_mode  &&  fchmod(fileno(*fromp),mode_while_renaming) == 0)
1619275970Scy		fchmod_set_mode = set_mode;
1620275970Scy#	endif
1621275970Scy	/* If bad_chmod_close, we must close before chmod.  */
1622275970Scy	Ozclose(fromp);
1623275970Scy	if (fchmod_set_mode<set_mode  &&  chmod(from, mode_while_renaming) != 0)
1624275970Scy	    return -1;
1625275970Scy
1626275970Scy	if (setmtime(from, mtime) != 0)
1627275970Scy		return -1;
1628275970Scy
1629275970Scy#	if !has_rename || bad_b_rename
1630275970Scy		/*
1631310419Sdelphij		* There's a short window of inconsistency
1632275970Scy		* during which TO does not exist.
1633275970Scy		*/
1634275970Scy		if (un_link(to) != 0  &&  errno != ENOENT)
1635275970Scy			return -1;
1636275970Scy#	endif
1637275970Scy
1638275970Scy#	if has_rename
1639275970Scy	    if (rename(from,to) != 0  &&  !(has_NFS && errno==ENOENT))
1640275970Scy		return -1;
1641275970Scy#	else
1642275970Scy	    if (do_link(from,to) != 0  ||  un_link(from) != 0)
1643289999Sglebius		return -1;
1644275970Scy#	endif
1645275970Scy
1646275970Scy#	if bad_NFS_rename
1647289999Sglebius	{
1648275970Scy	    /*
1649289999Sglebius	    * Check whether the rename falsely reported success.
1650289999Sglebius	    * A race condition can occur between the rename and the stat.
1651275970Scy	    */
1652289999Sglebius	    struct stat tostat;
1653289999Sglebius	    if (stat(to, &tostat) != 0)
1654289999Sglebius		return -1;
1655289999Sglebius	    if (! same_file(st, tostat, 0)) {
1656289999Sglebius		errno = EIO;
1657289999Sglebius		return -1;
1658275970Scy	    }
1659289999Sglebius	}
1660289999Sglebius#	endif
1661289999Sglebius
1662289999Sglebius#	if bad_a_rename
1663289999Sglebius	    if (0 < set_mode  &&  chmod(to, mode) != 0)
1664289999Sglebius		return -1;
1665289999Sglebius#	endif
1666289999Sglebius
1667275970Scy	return 0;
1668289999Sglebius}
1669289999Sglebius
1670289999Sglebius	int
1671289999Sglebiussetmtime(file, mtime)
1672289999Sglebius	char const *file;
1673289999Sglebius	time_t mtime;
1674289999Sglebius/* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1.  */
1675289999Sglebius{
1676289999Sglebius	static struct utimbuf amtime; /* static so unused fields are zero */
1677275970Scy	if (mtime == -1)
1678289999Sglebius		return 0;
1679275970Scy	amtime.actime = now();
1680275970Scy	amtime.modtime = mtime;
1681275970Scy	return utime(file, &amtime);
1682275970Scy}
1683275970Scy
1684275970Scy
1685275970Scy
1686275970Scy	int
1687275970Scyfindlock(delete, target)
1688275970Scy	int delete;
1689275970Scy	struct hshentry **target;
1690275970Scy/*
1691275970Scy * Find the first lock held by caller and return a pointer
1692275970Scy * to the locked delta; also removes the lock if DELETE.
1693275970Scy * If one lock, put it into *TARGET.
1694275970Scy * Return 0 for no locks, 1 for one, 2 for two or more.
1695289999Sglebius */
1696289999Sglebius{
1697289999Sglebius	register struct rcslock *next, **trail, **found;
1698275970Scy
1699289999Sglebius	found = 0;
1700289999Sglebius	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
1701275970Scy		if (strcmp(getcaller(), next->login)  ==  0) {
1702289999Sglebius			if (found) {
1703289999Sglebius				rcserror("multiple revisions locked by %s; please specify one", getcaller());
1704289999Sglebius				return 2;
1705289999Sglebius			}
1706289999Sglebius			found = trail;
1707289999Sglebius		}
1708289999Sglebius	if (!found)
1709275970Scy		return 0;
1710289999Sglebius	next = *found;
1711289999Sglebius	*target = next->delta;
1712289999Sglebius	if (delete) {
1713289999Sglebius		next->delta->lockedby = 0;
1714289999Sglebius		*found = next->nextlock;
1715289999Sglebius	}
1716289999Sglebius	return 1;
1717275970Scy}
1718289999Sglebius
1719289999Sglebius	int
1720289999Sglebiusaddlock(delta, verbose)
1721289999Sglebius	struct hshentry * delta;
1722289999Sglebius	int verbose;
1723289999Sglebius/*
1724289999Sglebius * Add a lock held by caller to DELTA and yield 1 if successful.
1725289999Sglebius * Print an error message if verbose and yield -1 if no lock is added because
1726289999Sglebius * DELTA is locked by somebody other than caller.
1727275970Scy * Return 0 if the caller already holds the lock.
1728289999Sglebius */
1729289999Sglebius{
1730285612Sdelphij	register struct rcslock *next;
1731275970Scy
1732275970Scy	for (next = Locks;  next;  next = next->nextlock)
1733275970Scy		if (cmpnum(delta->num, next->delta->num) == 0)
1734275970Scy			if (strcmp(getcaller(), next->login) == 0)
1735275970Scy				return 0;
1736275970Scy			else {
1737275970Scy				if (verbose)
1738275970Scy				  rcserror("Revision %s is already locked by %s.",
1739275970Scy					delta->num, next->login
1740275970Scy				  );
1741275970Scy				return -1;
1742275970Scy			}
1743275970Scy	next = ftalloc(struct rcslock);
1744275970Scy	delta->lockedby = next->login = getcaller();
1745275970Scy	next->delta = delta;
1746275970Scy	next->nextlock = Locks;
1747289999Sglebius	Locks = next;
1748285612Sdelphij	return 1;
1749275970Scy}
1750275970Scy
1751275970Scy
1752275970Scy	int
1753275970Scyaddsymbol(num, name, rebind)
1754275970Scy	char const *num, *name;
1755275970Scy	int rebind;
1756275970Scy/*
1757275970Scy * Associate with revision NUM the new symbolic NAME.
1758275970Scy * If NAME already exists and REBIND is set, associate NAME with NUM;
1759275970Scy * otherwise, print an error message and return false;
1760275970Scy * Return -1 if unsuccessful, 0 if no change, 1 if change.
1761289999Sglebius */
1762289999Sglebius{
1763289999Sglebius	register struct assoc *next;
1764289999Sglebius
1765289999Sglebius	for (next = Symbols;  next;  next = next->nextassoc)
1766289999Sglebius		if (strcmp(name, next->symbol)  ==  0)
1767289999Sglebius			if (strcmp(next->num,num) == 0)
1768289999Sglebius				return 0;
1769289999Sglebius			else if (rebind) {
1770275970Scy				next->num = num;
1771275970Scy				return 1;
1772289999Sglebius			} else {
1773275970Scy				rcserror("symbolic name %s already bound to %s",
1774275970Scy					name, next->num
1775275970Scy				);
1776275970Scy				return -1;
1777280849Scy			}
1778275970Scy	next = ftalloc(struct assoc);
1779275970Scy	next->symbol = name;
1780275970Scy	next->num = num;
1781275970Scy	next->nextassoc = Symbols;
1782275970Scy	Symbols = next;
1783275970Scy	return 1;
1784275970Scy}
1785275970Scy
1786275970Scy
1787275970Scy
1788285612Sdelphij	char const *
1789275970Scygetcaller()
1790275970Scy/* Get the caller's login name.  */
1791275970Scy{
1792275970Scy#	if has_setuid
1793275970Scy		return getusername(euid()!=ruid());
1794275970Scy#	else
1795275970Scy		return getusername(false);
1796275970Scy#	endif
1797275970Scy}
1798275970Scy
1799275970Scy
1800275970Scy	int
1801275970Scycheckaccesslist()
1802275970Scy/*
1803275970Scy * Return true if caller is the superuser, the owner of the
1804275970Scy * file, the access list is empty, or caller is on the access list.
1805275970Scy * Otherwise, print an error message and return false.
1806275970Scy */
1807275970Scy{
1808275970Scy	register struct access const *next;
1809275970Scy
1810275970Scy	if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
1811275970Scy		return true;
1812275970Scy
1813275970Scy	next = AccessList;
1814275970Scy	do {
1815275970Scy		if (strcmp(getcaller(), next->login)  ==  0)
1816275970Scy			return true;
1817275970Scy	} while ((next = next->nextaccess));
1818275970Scy
1819275970Scy	rcserror("user %s not on the access list", getcaller());
1820275970Scy	return false;
1821275970Scy}
1822275970Scy
1823275970Scy
1824275970Scy	int
1825275970Scydorewrite(lockflag, changed)
1826275970Scy	int lockflag, changed;
1827275970Scy/*
1828275970Scy * Do nothing if LOCKFLAG is zero.
1829 * Prepare to rewrite an RCS file if CHANGED is positive.
1830 * Stop rewriting if CHANGED is zero, because there won't be any changes.
1831 * Fail if CHANGED is negative.
1832 * Return 0 on success, -1 on failure.
1833 */
1834{
1835	int r = 0, e;
1836
1837	if (lockflag)
1838		if (changed) {
1839			if (changed < 0)
1840				return -1;
1841			putadmin();
1842			puttree(Head, frewrite);
1843			aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
1844			foutptr = frewrite;
1845		} else {
1846#			if bad_creat0
1847				int nr = !!frewrite, ne = 0;
1848#			endif
1849			ORCSclose();
1850			seteid();
1851			ignoreints();
1852#			if bad_creat0
1853				if (nr) {
1854					nr = un_link(newRCSname);
1855					ne = errno;
1856					keepdirtemp(newRCSname);
1857				}
1858#			endif
1859			r = un_link(lockname);
1860			e = errno;
1861			keepdirtemp(lockname);
1862			restoreints();
1863			setrid();
1864			if (r != 0)
1865				enerror(e, lockname);
1866#			if bad_creat0
1867				if (nr != 0) {
1868					enerror(ne, newRCSname);
1869					r = -1;
1870				}
1871#			endif
1872		}
1873	return r;
1874}
1875
1876	int
1877donerewrite(changed, newRCStime)
1878	int changed;
1879	time_t newRCStime;
1880/*
1881 * Finish rewriting an RCS file if CHANGED is nonzero.
1882 * Set its mode if CHANGED is positive.
1883 * Set its modification time to NEWRCSTIME unless it is -1.
1884 * Return 0 on success, -1 on failure.
1885 */
1886{
1887	int r = 0, e = 0;
1888#	if bad_creat0
1889		int lr, le;
1890#	endif
1891
1892	if (changed && !nerror) {
1893		if (finptr) {
1894			fastcopy(finptr, frewrite);
1895			Izclose(&finptr);
1896		}
1897		if (1 < RCSstat.st_nlink)
1898			rcswarn("breaking hard link");
1899		aflush(frewrite);
1900		seteid();
1901		ignoreints();
1902		r = chnamemod(
1903			&frewrite, newRCSname, RCSname, changed,
1904			RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
1905			newRCStime
1906		);
1907		e = errno;
1908		keepdirtemp(newRCSname);
1909#		if bad_creat0
1910			lr = un_link(lockname);
1911			le = errno;
1912			keepdirtemp(lockname);
1913#		endif
1914		restoreints();
1915		setrid();
1916		if (r != 0) {
1917			enerror(e, RCSname);
1918			error("saved in %s", newRCSname);
1919		}
1920#		if bad_creat0
1921			if (lr != 0) {
1922				enerror(le, lockname);
1923				r = -1;
1924			}
1925#		endif
1926	}
1927	return r;
1928}
1929
1930	void
1931ORCSclose()
1932{
1933	if (0 <= fdlock) {
1934		if (close(fdlock) != 0)
1935			efaterror(lockname);
1936		fdlock = -1;
1937	}
1938	Ozclose(&frewrite);
1939}
1940
1941	void
1942ORCSerror()
1943/*
1944* Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
1945* Do not report errors, since this may loop.  This is needed only because
1946* some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
1947* some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
1948* This isn't a completely reliable away to work around brain-damaged hosts,
1949* because of the gap between actual file opening and setting frewrite etc.,
1950* but it's better than nothing.
1951*/
1952{
1953	if (0 <= fdlock)
1954		VOID close(fdlock);
1955	if (frewrite)
1956		/* Avoid fclose, since stdio may not be reentrant.  */
1957		VOID close(fileno(frewrite));
1958}
1959