1/* RCS stream editor */
2
3/******************************************************************************
4 *                       edits the input file according to a
5 *                       script from stdin, generated by diff -n
6 *                       performs keyword expansion
7 ******************************************************************************
8 */
9
10/* Copyright 1982, 1988, 1989 Walter Tichy
11   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
12   Distributed under license by the Free Software Foundation, Inc.
13
14This file is part of RCS.
15
16RCS is free software; you can redistribute it and/or modify
17it under the terms of the GNU General Public License as published by
18the Free Software Foundation; either version 2, or (at your option)
19any later version.
20
21RCS is distributed in the hope that it will be useful,
22but WITHOUT ANY WARRANTY; without even the implied warranty of
23MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24GNU General Public License for more details.
25
26You should have received a copy of the GNU General Public License
27along with RCS; see the file COPYING.
28If not, write to the Free Software Foundation,
2959 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
30
31Report problems and direct all questions to:
32
33    rcs-bugs@cs.purdue.edu
34
35*/
36
37/*
38 * $Log: rcsedit.c,v $
39 * Revision 1.1  2003/06/11 15:56:09  darkwyrm
40 * Added rcs, gzip, sed, and associated utilities.
41 *
42 * Revision 5.19  1995/06/16 06:19:24  eggert
43 * Update FSF address.
44 *
45 * Revision 5.18  1995/06/01 16:23:43  eggert
46 * (dirtpname): No longer external.
47 * (do_link): Simplify logic.
48 * (finisheditline, finishedit): Replace Iseek/Itell with what they stand for.
49 * (fopen_update_truncate): Replace `#if' with `if'.
50 * (keyreplace, makedirtemp): dirlen(x) -> basefilename(x)-x.
51 *
52 * (edit_string): Fix bug: if !large_memory, a bogus trailing `@' was output
53 * at the end of incomplete lines.
54 *
55 * (keyreplace): Do not assume that seeking backwards
56 * at the start of a file will fail; on some systems it succeeds.
57 * Convert C- and Pascal-style comment starts to ` *' in comment leader.
58 *
59 * (rcswriteopen): Use fdSafer to get safer file descriptor.
60 * Open RCS file with FOPEN_RB.
61 *
62 * (chnamemod): Work around bad_NFS_rename bug; don't ignore un_link result.
63 * Fall back on chmod if fchmod fails, since it might be ENOSYS.
64 *
65 * (aflush): Move to rcslex.c.
66 *
67 * Revision 5.17  1994/03/20 04:52:58  eggert
68 * Normally calculate the $Log prefix from context, not from RCS file.
69 * Move setmtime here from rcsutil.c.  Add ORCSerror.  Remove lint.
70 *
71 * Revision 5.16  1993/11/03 17:42:27  eggert
72 * Add -z.  Add Name keyword.  If bad_unlink, ignore errno when unlink fails.
73 * Escape white space, $, and \ in keyword string file names.
74 * Don't output 2 spaces between date and time after Log.
75 *
76 * Revision 5.15  1992/07/28  16:12:44  eggert
77 * Some hosts have readlink but not ELOOP.  Avoid `unsigned'.
78 * Preserve dates more systematically.  Statement macro names now end in _.
79 *
80 * Revision 5.14  1992/02/17  23:02:24  eggert
81 * Add -T support.
82 *
83 * Revision 5.13  1992/01/24  18:44:19  eggert
84 * Add support for bad_chmod_close, bad_creat0.
85 *
86 * Revision 5.12  1992/01/06  02:42:34  eggert
87 * Add setmode parameter to chnamemod.  addsymbol now reports changes.
88 * while (E) ; -> while (E) continue;
89 *
90 * Revision 5.11  1991/11/03  01:11:44  eggert
91 * Move the warning about link breaking to where they're actually being broken.
92 *
93 * Revision 5.10  1991/10/07  17:32:46  eggert
94 * Support piece tables even if !has_mmap.  Fix rare NFS bugs.
95 *
96 * Revision 5.9  1991/09/17  19:07:40  eggert
97 * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
98 *
99 * Revision 5.8  1991/08/19  03:13:55  eggert
100 * Add piece tables, NFS bug workarounds.  Catch odd filenames.  Tune.
101 *
102 * Revision 5.7  1991/04/21  11:58:21  eggert
103 * Fix errno bugs.  Add -x, RCSINIT, MS-DOS support.
104 *
105 * Revision 5.6  1991/02/25  07:12:40  eggert
106 * Fix setuid bug.  Support new link behavior.  Work around broken "w+" fopen.
107 *
108 * Revision 5.5  1990/12/30  05:07:35  eggert
109 * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL).
110 *
111 * Revision 5.4  1990/11/01  05:03:40  eggert
112 * Permit arbitrary data in comment leaders.
113 *
114 * Revision 5.3  1990/09/11  02:41:13  eggert
115 * Tune expandline().
116 *
117 * Revision 5.2  1990/09/04  08:02:21  eggert
118 * Count RCS lines better.  Improve incomplete line handling.
119 *
120 * Revision 5.1  1990/08/29  07:13:56  eggert
121 * Add -kkvl.
122 * Fix bug when getting revisions to files ending in incomplete lines.
123 * Fix bug in comment leader expansion.
124 *
125 * Revision 5.0  1990/08/22  08:12:47  eggert
126 * Don't require final newline.
127 * Don't append "checked in with -k by " to logs,
128 * so that checking in a program with -k doesn't change it.
129 * Don't generate trailing white space for empty comment leader.
130 * Remove compile-time limits; use malloc instead.  Add -k, -V.
131 * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
132 * Ansify and Posixate.  Check diff's output.
133 *
134 * Revision 4.8  89/05/01  15:12:35  narten
135 * changed copyright header to reflect current distribution rules
136 *
137 * Revision 4.7  88/11/08  13:54:14  narten
138 * misplaced semicolon caused infinite loop
139 *
140 * Revision 4.6  88/08/09  19:12:45  eggert
141 * Shrink stdio code size; allow cc -R.
142 *
143 * Revision 4.5  87/12/18  11:38:46  narten
144 * Changes from the 43. version. Don't know the significance of the
145 * first change involving "rewind". Also, additional "lint" cleanup.
146 * (Guy Harris)
147 *
148 * Revision 4.4  87/10/18  10:32:21  narten
149 * Updating version numbers. Changes relative to version 1.1 actually
150 * relative to 4.1
151 *
152 * Revision 1.4  87/09/24  13:59:29  narten
153 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
154 * warnings)
155 *
156 * Revision 1.3  87/09/15  16:39:39  shepler
157 * added an initializatin of the variables editline and linecorr
158 * this will be done each time a file is processed.
159 * (there was an obscure bug where if co was used to retrieve multiple files
160 *  it would dump)
161 * fix attributed to  Roy Morris @FileNet Corp ...!felix!roy
162 *
163 * Revision 1.2  87/03/27  14:22:17  jenkins
164 * Port to suns
165 *
166 * Revision 4.1  83/05/12  13:10:30  wft
167 * Added new markers Id and RCSfile; added locker to Header and Id.
168 * Overhauled expandline completely() (problem with $01234567890123456789@).
169 * Moved trymatch() and marker table to rcskeys.c.
170 *
171 * Revision 3.7  83/05/12  13:04:39  wft
172 * Added retry to expandline to resume after failed match which ended in $.
173 * Fixed truncation problem for $19chars followed by@@.
174 * Log no longer expands full path of RCS file.
175 *
176 * Revision 3.6  83/05/11  16:06:30  wft
177 * added retry to expandline to resume after failed match which ended in $.
178 * Fixed truncation problem for $19chars followed by@@.
179 *
180 * Revision 3.5  82/12/04  13:20:56  wft
181 * Added expansion of keyword Locker.
182 *
183 * Revision 3.4  82/12/03  12:26:54  wft
184 * Added line number correction in case editing does not start at the
185 * beginning of the file.
186 * Changed keyword expansion to always print a space before closing KDELIM;
187 * Expansion for Header shortened.
188 *
189 * Revision 3.3  82/11/14  14:49:30  wft
190 * removed Suffix from keyword expansion. Replaced fclose with ffclose.
191 * keyreplace() gets log message from delta, not from curlogmsg.
192 * fixed expression overflow in while(c=putc(GETC....
193 * checked nil printing.
194 *
195 * Revision 3.2  82/10/18  21:13:39  wft
196 * I added checks for write errors during the co process, and renamed
197 * expandstring() to xpandstring().
198 *
199 * Revision 3.1  82/10/13  15:52:55  wft
200 * changed type of result of getc() from char to int.
201 * made keyword expansion loop in expandline() portable to machines
202 * without sign-extension.
203 */
204
205
206#include "rcsbase.h"
207
208libId(editId, "$Id: rcsedit.c 3476 2003-06-11 15:56:10Z darkwyrm $")
209
210static void editEndsPrematurely P((void)) exiting;
211static void editLineNumberOverflow P((void)) exiting;
212static void escape_string P((FILE*,char const*));
213static void keyreplace P((enum markers,struct hshentry const*,int,RILE*,FILE*,int));
214
215FILE *fcopy;		 /* result file descriptor			    */
216char const *resultname;	 /* result pathname				    */
217int locker_expansion;	 /* should the locker name be appended to Id val?   */
218#if !large_memory
219	static RILE *fedit; /* edit file descriptor */
220	static char const *editname; /* edit pathname */
221#endif
222static long editline; /* edit line counter; #lines before cursor   */
223static long linecorr; /* #adds - #deletes in each edit run.		    */
224               /*used to correct editline in case file is not rewound after */
225               /* applying one delta                                        */
226
227/* indexes into dirtpname */
228#define lockdirtp_index 0
229#define newRCSdirtp_index bad_creat0
230#define newworkdirtp_index (newRCSdirtp_index+1)
231#define DIRTEMPNAMES (newworkdirtp_index + 1)
232
233enum maker {notmade, real, effective};
234static struct buf dirtpname[DIRTEMPNAMES];	/* unlink these when done */
235static enum maker volatile dirtpmaker[DIRTEMPNAMES];	/* if these are set */
236#define lockname (dirtpname[lockdirtp_index].string)
237#define newRCSname (dirtpname[newRCSdirtp_index].string)
238
239
240#if has_NFS || bad_unlink
241	int
242un_link(s)
243	char const *s;
244/*
245 * Remove S, even if it is unwritable.
246 * Ignore unlink() ENOENT failures; NFS generates bogus ones.
247 */
248{
249#	if bad_unlink
250		if (unlink(s) == 0)
251			return 0;
252		else {
253			int e = errno;
254			/*
255			* Forge ahead even if errno == ENOENT; some completely
256			* brain-damaged hosts (e.g. PCTCP 2.2) yield ENOENT
257			* even for existing unwritable files.
258			*/
259			if (chmod(s, S_IWUSR) != 0) {
260				errno = e;
261				return -1;
262			}
263		}
264#	endif
265#	if has_NFS
266		return unlink(s)==0 || errno==ENOENT  ?  0  :  -1;
267#	else
268		return unlink(s);
269#	endif
270}
271#endif
272
273#if !has_rename
274#  if !has_NFS
275#	define do_link(s,t) link(s,t)
276#  else
277	static int do_link P((char const*,char const*));
278	static int
279do_link(s, t)
280	char const *s, *t;
281/* Link S to T, ignoring bogus EEXIST problems due to NFS failures.  */
282{
283	int r = link(s, t);
284
285	if (r != 0  &&  errno == EEXIST) {
286		struct stat sb, tb;
287		if (
288		    stat(s, &sb) == 0  &&
289		    stat(t, &tb) == 0  &&
290		    same_file(sb, tb, 0)
291		)
292			r = 0;
293		errno = EEXIST;
294	}
295	return r;
296}
297#  endif
298#endif
299
300
301	static void
302editEndsPrematurely()
303{
304	fatserror("edit script ends prematurely");
305}
306
307	static void
308editLineNumberOverflow()
309{
310	fatserror("edit script refers to line past end of file");
311}
312
313
314#if large_memory
315
316#if has_memmove
317#	define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
318#else
319	static void movelines P((Iptr_type*,Iptr_type const*,long));
320	static void
321movelines(s1, s2, n)
322	register Iptr_type *s1;
323	register Iptr_type const *s2;
324	register long n;
325{
326	if (s1 < s2)
327		do {
328			*s1++ = *s2++;
329		} while (--n);
330	else {
331		s1 += n;
332		s2 += n;
333		do {
334			*--s1 = *--s2;
335		} while (--n);
336	}
337}
338#endif
339
340static void deletelines P((long,long));
341static void finisheditline P((RILE*,FILE*,Iptr_type,struct hshentry const*));
342static void insertline P((long,Iptr_type));
343static void snapshotline P((FILE*,Iptr_type));
344
345/*
346 * `line' contains pointers to the lines in the currently `edited' file.
347 * It is a 0-origin array that represents linelim-gapsize lines.
348 * line[0 .. gap-1] and line[gap+gapsize .. linelim-1] hold pointers to lines.
349 * line[gap .. gap+gapsize-1] contains garbage.
350 *
351 * Any @s in lines are duplicated.
352 * Lines are terminated by \n, or (for a last partial line only) by single @.
353 */
354static Iptr_type *line;
355static size_t gap, gapsize, linelim;
356
357	static void
358insertline(n, l)
359	long n;
360	Iptr_type l;
361/* Before line N, insert line L.  N is 0-origin.  */
362{
363	if (linelim-gapsize < n)
364	    editLineNumberOverflow();
365	if (!gapsize)
366	    line =
367		!linelim ?
368			tnalloc(Iptr_type, linelim = gapsize = 1024)
369		: (
370			gap = gapsize = linelim,
371			trealloc(Iptr_type, line, linelim <<= 1)
372		);
373	if (n < gap)
374	    movelines(line+n+gapsize, line+n, gap-n);
375	else if (gap < n)
376	    movelines(line+gap, line+gap+gapsize, n-gap);
377
378	line[n] = l;
379	gap = n + 1;
380	gapsize--;
381}
382
383	static void
384deletelines(n, nlines)
385	long n, nlines;
386/* Delete lines N through N+NLINES-1.  N is 0-origin.  */
387{
388	long l = n + nlines;
389	if (linelim-gapsize < l  ||  l < n)
390	    editLineNumberOverflow();
391	if (l < gap)
392	    movelines(line+l+gapsize, line+l, gap-l);
393	else if (gap < n)
394	    movelines(line+gap, line+gap+gapsize, n-gap);
395
396	gap = n;
397	gapsize += nlines;
398}
399
400	static void
401snapshotline(f, l)
402	register FILE *f;
403	register Iptr_type l;
404{
405	register int c;
406	do {
407		if ((c = *l++) == SDELIM  &&  *l++ != SDELIM)
408			return;
409		aputc_(c, f)
410	} while (c != '\n');
411}
412
413	void
414snapshotedit(f)
415	FILE *f;
416/* Copy the current state of the edits to F.  */
417{
418	register Iptr_type *p, *lim, *l=line;
419	for (p=l, lim=l+gap;  p<lim;  )
420		snapshotline(f, *p++);
421	for (p+=gapsize, lim=l+linelim;  p<lim;  )
422		snapshotline(f, *p++);
423}
424
425	static void
426finisheditline(fin, fout, l, delta)
427	RILE *fin;
428	FILE *fout;
429	Iptr_type l;
430	struct hshentry const *delta;
431{
432	fin->ptr = l;
433	if (expandline(fin, fout, delta, true, (FILE*)0, true)  <  0)
434		faterror("finisheditline internal error");
435}
436
437	void
438finishedit(delta, outfile, done)
439	struct hshentry const *delta;
440	FILE *outfile;
441	int done;
442/*
443 * Doing expansion if DELTA is set, output the state of the edits to OUTFILE.
444 * But do nothing unless DONE is set (which means we are on the last pass).
445 */
446{
447	if (done) {
448		openfcopy(outfile);
449		outfile = fcopy;
450		if (!delta)
451			snapshotedit(outfile);
452		else {
453			register Iptr_type *p, *lim, *l = line;
454			register RILE *fin = finptr;
455			Iptr_type here = fin->ptr;
456			for (p=l, lim=l+gap;  p<lim;  )
457				finisheditline(fin, outfile, *p++, delta);
458			for (p+=gapsize, lim=l+linelim;  p<lim;  )
459				finisheditline(fin, outfile, *p++, delta);
460			fin->ptr = here;
461		}
462	}
463}
464
465/* Open a temporary NAME for output, truncating any previous contents.  */
466#   define fopen_update_truncate(name) fopenSafer(name, FOPEN_W_WORK)
467#else /* !large_memory */
468    static FILE * fopen_update_truncate P((char const*));
469    static FILE *
470fopen_update_truncate(name)
471    char const *name;
472{
473	if (bad_fopen_wplus  &&  un_link(name) != 0)
474		efaterror(name);
475	return fopenSafer(name, FOPEN_WPLUS_WORK);
476}
477#endif
478
479
480	void
481openfcopy(f)
482	FILE *f;
483{
484	if (!(fcopy = f)) {
485		if (!resultname)
486			resultname = maketemp(2);
487		if (!(fcopy = fopen_update_truncate(resultname)))
488			efaterror(resultname);
489	}
490}
491
492
493#if !large_memory
494
495	static void swapeditfiles P((FILE*));
496	static void
497swapeditfiles(outfile)
498	FILE *outfile;
499/* Function: swaps resultname and editname, assigns fedit=fcopy,
500 * and rewinds fedit for reading.  Set fcopy to outfile if nonnull;
501 * otherwise, set fcopy to be resultname opened for reading and writing.
502 */
503{
504	char const *tmpptr;
505
506	editline = 0;  linecorr = 0;
507	Orewind(fcopy);
508	fedit = fcopy;
509	tmpptr=editname; editname=resultname; resultname=tmpptr;
510	openfcopy(outfile);
511}
512
513	void
514snapshotedit(f)
515	FILE *f;
516/* Copy the current state of the edits to F.  */
517{
518	finishedit((struct hshentry *)0, (FILE*)0, false);
519	fastcopy(fedit, f);
520	Irewind(fedit);
521}
522
523	void
524finishedit(delta, outfile, done)
525	struct hshentry const *delta;
526	FILE *outfile;
527	int done;
528/* copy the rest of the edit file and close it (if it exists).
529 * if delta, perform keyword substitution at the same time.
530 * If DONE is set, we are finishing the last pass.
531 */
532{
533	register RILE *fe;
534	register FILE *fc;
535
536	fe = fedit;
537	if (fe) {
538		fc = fcopy;
539		if (delta) {
540			while (1 < expandline(fe,fc,delta,false,(FILE*)0,true))
541				;
542                } else {
543			fastcopy(fe,fc);
544                }
545		Ifclose(fe);
546        }
547	if (!done)
548		swapeditfiles(outfile);
549}
550#endif
551
552
553
554#if large_memory
555#	define copylines(upto,delta) (editline = (upto))
556#else
557	static void copylines P((long,struct hshentry const*));
558	static void
559copylines(upto, delta)
560	register long upto;
561	struct hshentry const *delta;
562/*
563 * Copy input lines editline+1..upto from fedit to fcopy.
564 * If delta, keyword expansion is done simultaneously.
565 * editline is updated. Rewinds a file only if necessary.
566 */
567{
568	register int c;
569	declarecache;
570	register FILE *fc;
571	register RILE *fe;
572
573	if (upto < editline) {
574                /* swap files */
575		finishedit((struct hshentry *)0, (FILE*)0, false);
576                /* assumes edit only during last pass, from the beginning*/
577        }
578	fe = fedit;
579	fc = fcopy;
580	if (editline < upto)
581	    if (delta)
582		do {
583		    if (expandline(fe,fc,delta,false,(FILE*)0,true) <= 1)
584			editLineNumberOverflow();
585		} while (++editline < upto);
586	    else {
587		setupcache(fe); cache(fe);
588		do {
589			do {
590				cachegeteof_(c, editLineNumberOverflow();)
591				aputc_(c, fc)
592			} while (c != '\n');
593		} while (++editline < upto);
594		uncache(fe);
595	    }
596}
597#endif
598
599
600
601	void
602xpandstring(delta)
603	struct hshentry const *delta;
604/* Function: Reads a string terminated by SDELIM from finptr and writes it
605 * to fcopy. Double SDELIM is replaced with single SDELIM.
606 * Keyword expansion is performed with data from delta.
607 * If foutptr is nonnull, the string is also copied unchanged to foutptr.
608 */
609{
610	while (1 < expandline(finptr,fcopy,delta,true,foutptr,true))
611		continue;
612}
613
614
615	void
616copystring()
617/* Function: copies a string terminated with a single SDELIM from finptr to
618 * fcopy, replacing all double SDELIM with a single SDELIM.
619 * If foutptr is nonnull, the string also copied unchanged to foutptr.
620 * editline is incremented by the number of lines copied.
621 * Assumption: next character read is first string character.
622 */
623{	register c;
624	declarecache;
625	register FILE *frew, *fcop;
626	register int amidline;
627	register RILE *fin;
628
629	fin = finptr;
630	setupcache(fin); cache(fin);
631	frew = foutptr;
632	fcop = fcopy;
633	amidline = false;
634	for (;;) {
635		GETC_(frew,c)
636		switch (c) {
637		    case '\n':
638			++editline;
639			++rcsline;
640			amidline = false;
641			break;
642		    case SDELIM:
643			GETC_(frew,c)
644			if (c != SDELIM) {
645				/* end of string */
646				nextc = c;
647				editline += amidline;
648				uncache(fin);
649				return;
650			}
651			/* fall into */
652		    default:
653			amidline = true;
654			break;
655                }
656		aputc_(c,fcop)
657        }
658}
659
660
661	void
662enterstring()
663/* Like copystring, except the string is put into the edit data structure.  */
664{
665#if !large_memory
666	editname = 0;
667	fedit = 0;
668	editline = linecorr = 0;
669	resultname = maketemp(1);
670	if (!(fcopy = fopen_update_truncate(resultname)))
671		efaterror(resultname);
672	copystring();
673#else
674	register int c;
675	declarecache;
676	register FILE *frew;
677	register long e, oe;
678	register int amidline, oamidline;
679	register Iptr_type optr;
680	register RILE *fin;
681
682	e = 0;
683	gap = 0;
684	gapsize = linelim;
685	fin = finptr;
686	setupcache(fin); cache(fin);
687	advise_access(fin, MADV_NORMAL);
688	frew = foutptr;
689	amidline = false;
690	for (;;) {
691		optr = cacheptr();
692		GETC_(frew,c)
693		oamidline = amidline;
694		oe = e;
695		switch (c) {
696		    case '\n':
697			++e;
698			++rcsline;
699			amidline = false;
700			break;
701		    case SDELIM:
702			GETC_(frew,c)
703			if (c != SDELIM) {
704				/* end of string */
705				nextc = c;
706				editline = e + amidline;
707				linecorr = 0;
708				uncache(fin);
709				return;
710			}
711			/* fall into */
712		    default:
713			amidline = true;
714			break;
715		}
716		if (!oamidline)
717			insertline(oe, optr);
718	}
719#endif
720}
721
722
723
724
725	void
726#if large_memory
727edit_string()
728#else
729  editstring(delta)
730	struct hshentry const *delta;
731#endif
732/*
733 * Read an edit script from finptr and applies it to the edit file.
734#if !large_memory
735 * The result is written to fcopy.
736 * If delta, keyword expansion is performed simultaneously.
737 * If running out of lines in fedit, fedit and fcopy are swapped.
738 * editname is the name of the file that goes with fedit.
739#endif
740 * If foutptr is set, the edit script is also copied verbatim to foutptr.
741 * Assumes that all these files are open.
742 * resultname is the name of the file that goes with fcopy.
743 * Assumes the next input character from finptr is the first character of
744 * the edit script. Resets nextc on exit.
745 */
746{
747        int ed; /* editor command */
748        register int c;
749	declarecache;
750	register FILE *frew;
751#	if !large_memory
752		register FILE *f;
753		long line_lim = LONG_MAX;
754		register RILE *fe;
755#	endif
756	register long i;
757	register RILE *fin;
758#	if large_memory
759		register long j;
760#	endif
761	struct diffcmd dc;
762
763        editline += linecorr; linecorr=0; /*correct line number*/
764	frew = foutptr;
765	fin = finptr;
766	setupcache(fin);
767	initdiffcmd(&dc);
768	while (0  <=  (ed = getdiffcmd(fin,true,frew,&dc)))
769#if !large_memory
770		if (line_lim <= dc.line1)
771			editLineNumberOverflow();
772		else
773#endif
774		if (!ed) {
775			copylines(dc.line1-1, delta);
776                        /* skip over unwanted lines */
777			i = dc.nlines;
778			linecorr -= i;
779			editline += i;
780#			if large_memory
781			    deletelines(editline+linecorr, i);
782#			else
783			    fe = fedit;
784			    do {
785                                /*skip next line*/
786				do {
787				    Igeteof_(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } )
788				} while (c != '\n');
789			    } while (--i);
790#			endif
791		} else {
792			/* Copy lines without deleting any.  */
793			copylines(dc.line1, delta);
794			i = dc.nlines;
795#			if large_memory
796				j = editline+linecorr;
797#			endif
798			linecorr += i;
799#if !large_memory
800			f = fcopy;
801			if (delta)
802			    do {
803				switch (expandline(fin,f,delta,true,frew,true)){
804				    case 0: case 1:
805					if (i==1)
806					    return;
807					/* fall into */
808				    case -1:
809					editEndsPrematurely();
810				}
811			    } while (--i);
812			else
813#endif
814			{
815			    cache(fin);
816			    do {
817#				if large_memory
818				    insertline(j++, cacheptr());
819#				endif
820				for (;;) {
821				    GETC_(frew, c)
822				    if (c==SDELIM) {
823					GETC_(frew, c)
824					if (c!=SDELIM) {
825					    if (--i)
826						editEndsPrematurely();
827					    nextc = c;
828					    uncache(fin);
829					    return;
830					}
831				    }
832#				    if !large_memory
833					aputc_(c, f)
834#				    endif
835				    if (c == '\n')
836					break;
837				}
838				++rcsline;
839			    } while (--i);
840			    uncache(fin);
841			}
842                }
843}
844
845
846
847/* The rest is for keyword expansion */
848
849
850
851	int
852expandline(infile, outfile, delta, delimstuffed, frewfile, dolog)
853	RILE *infile;
854	FILE *outfile, *frewfile;
855	struct hshentry const *delta;
856	int delimstuffed, dolog;
857/*
858 * Read a line from INFILE and write it to OUTFILE.
859 * Do keyword expansion with data from DELTA.
860 * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
861 * If FREWFILE is set, copy the line unchanged to FREWFILE.
862 * DELIMSTUFFED must be true if FREWFILE is set.
863 * Append revision history to log only if DOLOG is set.
864 * Yields -1 if no data is copied, 0 if an incomplete line is copied,
865 * 2 if a complete line is copied; adds 1 to yield if expansion occurred.
866 */
867{
868	register c;
869	declarecache;
870	register FILE *out, *frew;
871	register char * tp;
872	register int e, ds, r;
873	char const *tlim;
874	static struct buf keyval;
875        enum markers matchresult;
876
877	setupcache(infile); cache(infile);
878	out = outfile;
879	frew = frewfile;
880	ds = delimstuffed;
881	bufalloc(&keyval, keylength+3);
882	e = 0;
883	r = -1;
884
885        for (;;) {
886	    if (ds)
887		GETC_(frew, c)
888	    else
889		cachegeteof_(c, goto uncache_exit;)
890	    for (;;) {
891		switch (c) {
892		    case SDELIM:
893			if (ds) {
894			    GETC_(frew, c)
895			    if (c != SDELIM) {
896                                /* end of string */
897                                nextc=c;
898				goto uncache_exit;
899			    }
900			}
901			/* fall into */
902		    default:
903			aputc_(c,out)
904			r = 0;
905			break;
906
907		    case '\n':
908			rcsline += ds;
909			aputc_(c,out)
910			r = 2;
911			goto uncache_exit;
912
913		    case KDELIM:
914			r = 0;
915                        /* check for keyword */
916                        /* first, copy a long enough string into keystring */
917			tp = keyval.string;
918			*tp++ = KDELIM;
919			for (;;) {
920			    if (ds)
921				GETC_(frew, c)
922			    else
923				cachegeteof_(c, goto keystring_eof;)
924			    if (tp <= &keyval.string[keylength])
925				switch (ctab[c]) {
926				    case LETTER: case Letter:
927					*tp++ = c;
928					continue;
929				    default:
930					break;
931				}
932			    break;
933                        }
934			*tp++ = c; *tp = '\0';
935			matchresult = trymatch(keyval.string+1);
936			if (matchresult==Nomatch) {
937				tp[-1] = 0;
938				aputs(keyval.string, out);
939				continue;   /* last c handled properly */
940			}
941
942			/* Now we have a keyword terminated with a K/VDELIM */
943			if (c==VDELIM) {
944			      /* try to find closing KDELIM, and replace value */
945			      tlim = keyval.string + keyval.size;
946			      for (;;) {
947				      if (ds)
948					GETC_(frew, c)
949				      else
950					cachegeteof_(c, goto keystring_eof;)
951				      if (c=='\n' || c==KDELIM)
952					break;
953				      *tp++ =c;
954				      if (tlim <= tp)
955					  tp = bufenlarge(&keyval, &tlim);
956				      if (c==SDELIM && ds) { /*skip next SDELIM */
957						GETC_(frew, c)
958						if (c != SDELIM) {
959							/* end of string before closing KDELIM or newline */
960							nextc = c;
961							goto keystring_eof;
962						}
963				      }
964			      }
965			      if (c!=KDELIM) {
966				    /* couldn't find closing KDELIM -- give up */
967				    *tp = 0;
968				    aputs(keyval.string, out);
969				    continue;   /* last c handled properly */
970			      }
971			}
972			/* now put out the new keyword value */
973			uncache(infile);
974			keyreplace(matchresult, delta, ds, infile, out, dolog);
975			cache(infile);
976			e = 1;
977			break;
978                }
979		break;
980	    }
981        }
982
983    keystring_eof:
984	*tp = 0;
985	aputs(keyval.string, out);
986    uncache_exit:
987	uncache(infile);
988	return r + e;
989}
990
991
992	static void
993escape_string(out, s)
994	register FILE *out;
995	register char const *s;
996/* Output to OUT the string S, escaping chars that would break `ci -k'.  */
997{
998    register char c;
999    for (;;)
1000	switch ((c = *s++)) {
1001	    case 0: return;
1002	    case '\t': aputs("\\t", out); break;
1003	    case '\n': aputs("\\n", out); break;
1004	    case ' ': aputs("\\040", out); break;
1005	    case KDELIM: aputs("\\044", out); break;
1006	    case '\\': if (VERSION(5)<=RCSversion) {aputs("\\\\", out); break;}
1007	    /* fall into */
1008	    default: aputc_(c, out) break;
1009	}
1010}
1011
1012char const ciklog[ciklogsize] = "checked in with -k by ";
1013
1014	static void
1015keyreplace(marker, delta, delimstuffed, infile, out, dolog)
1016	enum markers marker;
1017	register struct hshentry const *delta;
1018	int delimstuffed;
1019	RILE *infile;
1020	register FILE *out;
1021	int dolog;
1022/* function: outputs the keyword value(s) corresponding to marker.
1023 * Attributes are derived from delta.
1024 */
1025{
1026	register char const *sp, *cp, *date;
1027	register int c;
1028	register size_t cs, cw, ls;
1029	char const *sp1;
1030	char datebuf[datesize + zonelenmax];
1031	int RCSv;
1032	int exp;
1033
1034	sp = Keyword[(int)marker];
1035	exp = Expand;
1036	date = delta->date;
1037	RCSv = RCSversion;
1038
1039	if (exp != VAL_EXPAND)
1040	    aprintf(out, "%c%s", KDELIM, sp);
1041	if (exp != KEY_EXPAND) {
1042
1043	    if (exp != VAL_EXPAND)
1044		aprintf(out, "%c%c", VDELIM,
1045			marker==Log && RCSv<VERSION(5)  ?  '\t'  :  ' '
1046		);
1047
1048	    switch (marker) {
1049	    case Author:
1050		aputs(delta->author, out);
1051                break;
1052	    case Date:
1053		aputs(date2str(date,datebuf), out);
1054                break;
1055	    case Id:
1056	    case Header:
1057		escape_string(out,
1058			marker==Id || RCSv<VERSION(4)
1059			? basefilename(RCSname)
1060			: getfullRCSname()
1061		);
1062		aprintf(out, " %s %s %s %s",
1063			delta->num,
1064			date2str(date, datebuf),
1065			delta->author,
1066			  RCSv==VERSION(3) && delta->lockedby ? "Locked"
1067			: delta->state
1068		);
1069		if (delta->lockedby)
1070		    if (VERSION(5) <= RCSv) {
1071			if (locker_expansion || exp==KEYVALLOCK_EXPAND)
1072			    aprintf(out, " %s", delta->lockedby);
1073		    } else if (RCSv == VERSION(4))
1074			aprintf(out, " Locker: %s", delta->lockedby);
1075                break;
1076	    case Locker:
1077		if (delta->lockedby)
1078		    if (
1079				locker_expansion
1080			||	exp == KEYVALLOCK_EXPAND
1081			||	RCSv <= VERSION(4)
1082		    )
1083			aputs(delta->lockedby, out);
1084                break;
1085	    case Log:
1086	    case RCSfile:
1087		escape_string(out, basefilename(RCSname));
1088                break;
1089	    case Name:
1090		if (delta->name)
1091			aputs(delta->name, out);
1092		break;
1093	    case Revision:
1094		aputs(delta->num, out);
1095                break;
1096	    case Source:
1097		escape_string(out, getfullRCSname());
1098                break;
1099	    case State:
1100		aputs(delta->state, out);
1101                break;
1102	    default:
1103		break;
1104	    }
1105	    if (exp != VAL_EXPAND)
1106		afputc(' ', out);
1107	}
1108	if (exp != VAL_EXPAND)
1109	    afputc(KDELIM, out);
1110
1111	if (marker == Log   &&  dolog) {
1112		struct buf leader;
1113
1114		sp = delta->log.string;
1115		ls = delta->log.size;
1116		if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
1117			return;
1118		bufautobegin(&leader);
1119		if (RCSversion < VERSION(5)) {
1120		    cp = Comment.string;
1121		    cs = Comment.size;
1122		} else {
1123		    int kdelim_found = 0;
1124		    Ioffset_type chars_read = Itell(infile);
1125		    declarecache;
1126		    setupcache(infile); cache(infile);
1127
1128		    c = 0; /* Pacify `gcc -Wall'.  */
1129
1130		    /*
1131		    * Back up to the start of the current input line,
1132		    * setting CS to the number of characters before `$Log'.
1133		    */
1134		    cs = 0;
1135		    for (;;) {
1136			if (!--chars_read)
1137			    goto done_backing_up;
1138			cacheunget_(infile, c)
1139			if (c == '\n')
1140			    break;
1141			if (c == SDELIM  &&  delimstuffed) {
1142			    if (!--chars_read)
1143				break;
1144			    cacheunget_(infile, c)
1145			    if (c != SDELIM) {
1146				cacheget_(c)
1147				break;
1148			    }
1149			}
1150			cs += kdelim_found;
1151			kdelim_found |= c==KDELIM;
1152		    }
1153		    cacheget_(c)
1154		  done_backing_up:;
1155
1156		    /* Copy characters before `$Log' into LEADER.  */
1157		    bufalloc(&leader, cs);
1158		    cp = leader.string;
1159		    for (cw = 0;  cw < cs;  cw++) {
1160			leader.string[cw] = c;
1161			if (c == SDELIM  &&  delimstuffed)
1162			    cacheget_(c)
1163			cacheget_(c)
1164		    }
1165
1166		    /* Convert traditional C or Pascal leader to ` *'.  */
1167		    for (cw = 0;  cw < cs;  cw++)
1168			if (ctab[(unsigned char) cp[cw]] != SPACE)
1169			    break;
1170		    if (
1171			cw+1 < cs
1172			&&  cp[cw+1] == '*'
1173			&&  (cp[cw] == '/'  ||  cp[cw] == '(')
1174		    ) {
1175			size_t i = cw+1;
1176			for (;;)
1177			    if (++i == cs) {
1178				warn(
1179				    "`%c* $Log' is obsolescent; use ` * $Log'.",
1180				    cp[cw]
1181				);
1182				leader.string[cw] = ' ';
1183				break;
1184			    } else if (ctab[(unsigned char) cp[i]] != SPACE)
1185				break;
1186		    }
1187
1188		    /* Skip `$Log ... $' string.  */
1189		    do {
1190			cacheget_(c)
1191		    } while (c != KDELIM);
1192		    uncache(infile);
1193		}
1194		afputc('\n', out);
1195		awrite(cp, cs, out);
1196		sp1 = date2str(date, datebuf);
1197		if (VERSION(5) <= RCSv) {
1198		    aprintf(out, "Revision %s  %s  %s",
1199			delta->num, sp1, delta->author
1200		    );
1201		} else {
1202		    /* oddity: 2 spaces between date and time, not 1 as usual */
1203		    sp1 = strchr(sp1, ' ');
1204		    aprintf(out, "Revision %s  %.*s %s  %s",
1205			delta->num, (int)(sp1-datebuf), datebuf, sp1,
1206			delta->author
1207		    );
1208		}
1209		/* Do not include state: it may change and is not updated.  */
1210		cw = cs;
1211		if (VERSION(5) <= RCSv)
1212		    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
1213			continue;
1214		for (;;) {
1215		    afputc('\n', out);
1216		    awrite(cp, cw, out);
1217		    if (!ls)
1218			break;
1219		    --ls;
1220		    c = *sp++;
1221		    if (c != '\n') {
1222			awrite(cp+cw, cs-cw, out);
1223			do {
1224			    afputc(c,out);
1225			    if (!ls)
1226				break;
1227			    --ls;
1228			    c = *sp++;
1229			} while (c != '\n');
1230		    }
1231		}
1232		bufautoend(&leader);
1233	}
1234}
1235
1236#if has_readlink
1237	static int resolve_symlink P((struct buf*));
1238	static int
1239resolve_symlink(L)
1240	struct buf *L;
1241/*
1242 * If L is a symbolic link, resolve it to the name that it points to.
1243 * If unsuccessful, set errno and yield -1.
1244 * If it points to an existing file, yield 1.
1245 * Otherwise, set errno=ENOENT and yield 0.
1246 */
1247{
1248	char *b, a[SIZEABLE_PATH];
1249	int e;
1250	size_t s;
1251	ssize_t r;
1252	struct buf bigbuf;
1253	int linkcount = MAXSYMLINKS;
1254
1255	b = a;
1256	s = sizeof(a);
1257	bufautobegin(&bigbuf);
1258	while ((r = readlink(L->string,b,s))  !=  -1)
1259	    if (r == s) {
1260		bufalloc(&bigbuf, s<<1);
1261		b = bigbuf.string;
1262		s = bigbuf.size;
1263	    } else if (!linkcount--) {
1264#		ifndef ELOOP
1265		    /*
1266		    * Some pedantic Posix 1003.1-1990 hosts have readlink
1267		    * but not ELOOP.  Approximate ELOOP with EMLINK.
1268		    */
1269#		    define ELOOP EMLINK
1270#		endif
1271		errno = ELOOP;
1272		return -1;
1273	    } else {
1274		/* Splice symbolic link into L.  */
1275		b[r] = '\0';
1276		L->string[
1277		  ROOTPATH(b)  ?  0  :  basefilename(L->string) - L->string
1278		] = '\0';
1279		bufscat(L, b);
1280	    }
1281	e = errno;
1282	bufautoend(&bigbuf);
1283	errno = e;
1284	switch (e) {
1285	    case readlink_isreg_errno: return 1;
1286	    case ENOENT: return 0;
1287	    default: return -1;
1288	}
1289}
1290#endif
1291
1292	RILE *
1293rcswriteopen(RCSbuf, status, mustread)
1294	struct buf *RCSbuf;
1295	struct stat *status;
1296	int mustread;
1297/*
1298 * Create the lock file corresponding to RCSBUF.
1299 * Then try to open RCSBUF for reading and yield its RILE* descriptor.
1300 * Put its status into *STATUS too.
1301 * MUSTREAD is true if the file must already exist, too.
1302 * If all goes well, discard any previously acquired locks,
1303 * and set fdlock to the file descriptor of the RCS lockfile.
1304 */
1305{
1306	register char *tp;
1307	register char const *sp, *RCSpath, *x;
1308	RILE *f;
1309	size_t l;
1310	int e, exists, fdesc, fdescSafer, r, waslocked;
1311	struct buf *dirt;
1312	struct stat statbuf;
1313
1314	waslocked  =  0 <= fdlock;
1315	exists =
1316#		if has_readlink
1317			resolve_symlink(RCSbuf);
1318#		else
1319			    stat(RCSbuf->string, &statbuf) == 0  ?  1
1320			:   errno==ENOENT ? 0 : -1;
1321#		endif
1322	if (exists < (mustread|waslocked))
1323		/*
1324		 * There's an unusual problem with the RCS file;
1325		 * or the RCS file doesn't exist,
1326		 * and we must read or we already have a lock elsewhere.
1327		 */
1328		return 0;
1329
1330	RCSpath = RCSbuf->string;
1331	sp = basefilename(RCSpath);
1332	l = sp - RCSpath;
1333	dirt = &dirtpname[waslocked];
1334	bufscpy(dirt, RCSpath);
1335	tp = dirt->string + l;
1336	x = rcssuffix(RCSpath);
1337#	if has_readlink
1338	    if (!x) {
1339		error("symbolic link to non RCS file `%s'", RCSpath);
1340		errno = EINVAL;
1341		return 0;
1342	    }
1343#	endif
1344	if (*sp == *x) {
1345		error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
1346		errno = EINVAL;
1347		return 0;
1348	}
1349	/* Create a lock filename that is a function of the RCS filename.  */
1350	if (*x) {
1351		/*
1352		 * The suffix is nonempty.
1353		 * The lock filename is the first char of of the suffix,
1354		 * followed by the RCS filename with last char removed.  E.g.:
1355		 *	foo,v	RCS filename with suffix ,v
1356		 *	,foo,	lock filename
1357		 */
1358		*tp++ = *x;
1359		while (*sp)
1360			*tp++ = *sp++;
1361		*--tp = 0;
1362	} else {
1363		/*
1364		 * The suffix is empty.
1365		 * The lock filename is the RCS filename
1366		 * with last char replaced by '_'.
1367		 */
1368		while ((*tp++ = *sp++))
1369			continue;
1370		tp -= 2;
1371		if (*tp == '_') {
1372			error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
1373			errno = EINVAL;
1374			return 0;
1375		}
1376		*tp = '_';
1377	}
1378
1379	sp = dirt->string;
1380
1381	f = 0;
1382
1383	/*
1384	* good news:
1385	*	open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
1386	*	is atomic according to Posix 1003.1-1990.
1387	* bad news:
1388	*	NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
1389	* good news:
1390	*	(O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity
1391	*	even with NFS.
1392	* bad news:
1393	*	If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
1394	*	guarantee atomicity.
1395	* good news:
1396	*	Root-over-the-wire NFS access is rare for security reasons.
1397	*	This bug has never been reported in practice with RCS.
1398	* So we don't worry about this bug.
1399	*
1400	* An even rarer NFS bug can occur when clients retry requests.
1401	* This can happen in the usual case of NFS over UDP.
1402	* Suppose client A releases a lock by renaming ",f," to "f,v" at
1403	* about the same time that client B obtains a lock by creating ",f,",
1404	* and suppose A's first rename request is delayed, so A reissues it.
1405	* The sequence of events might be:
1406	*	A sends rename(",f,", "f,v")
1407	*	B sends create(",f,")
1408	*	A sends retry of rename(",f,", "f,v")
1409	*	server receives, does, and acknowledges A's first rename()
1410	*	A receives acknowledgment, and its RCS program exits
1411	*	server receives, does, and acknowledges B's create()
1412	*	server receives, does, and acknowledges A's retry of rename()
1413	* This not only wrongly deletes B's lock, it removes the RCS file!
1414	* Most NFS implementations have idempotency caches that usually prevent
1415	* this scenario, but such caches are finite and can be overrun.
1416	* This problem afflicts not only RCS, which uses open() and rename()
1417	* to get and release locks; it also afflicts the traditional
1418	* Unix method of using link() and unlink() to get and release locks,
1419	* and the less traditional method of using mkdir() and rmdir().
1420	* There is no easy workaround.
1421	* Any new method based on lockf() seemingly would be incompatible with
1422	* the old methods; besides, lockf() is notoriously buggy under NFS.
1423	* Since this problem afflicts scads of Unix programs, but is so rare
1424	* that nobody seems to be worried about it, we won't worry either.
1425	*/
1426#	if !open_can_creat
1427#		define create(f) creat(f, OPEN_CREAT_READONLY)
1428#	else
1429#		define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
1430#	endif
1431
1432	catchints();
1433	ignoreints();
1434
1435	/*
1436	 * Create a lock file for an RCS file.  This should be atomic, i.e.
1437	 * if two processes try it simultaneously, at most one should succeed.
1438	 */
1439	seteid();
1440	fdesc = create(sp);
1441	fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr.  */
1442	e = errno;
1443	setrid();
1444
1445	if (0 <= fdesc)
1446		dirtpmaker[0] = effective;
1447
1448	if (fdescSafer < 0) {
1449		if (e == EACCES  &&  stat(sp,&statbuf) == 0)
1450			/* The RCS file is busy.  */
1451			e = EEXIST;
1452	} else {
1453		e = ENOENT;
1454		if (exists) {
1455		    f = Iopen(RCSpath, FOPEN_RB, status);
1456		    e = errno;
1457		    if (f && waslocked) {
1458			/* Discard the previous lock in favor of this one.  */
1459			ORCSclose();
1460			seteid();
1461			r = un_link(lockname);
1462			e = errno;
1463			setrid();
1464			if (r != 0)
1465			    enfaterror(e, lockname);
1466			bufscpy(&dirtpname[lockdirtp_index], sp);
1467		    }
1468		}
1469		fdlock = fdescSafer;
1470	}
1471
1472	restoreints();
1473
1474	errno = e;
1475	return f;
1476}
1477
1478	void
1479keepdirtemp(name)
1480	char const *name;
1481/* Do not unlink name, either because it's not there any more,
1482 * or because it has already been unlinked.
1483 */
1484{
1485	register int i;
1486	for (i=DIRTEMPNAMES; 0<=--i; )
1487		if (dirtpname[i].string == name) {
1488			dirtpmaker[i] = notmade;
1489			return;
1490		}
1491	faterror("keepdirtemp");
1492}
1493
1494	char const *
1495makedirtemp(isworkfile)
1496	int isworkfile;
1497/*
1498 * Create a unique pathname and store it into dirtpname.
1499 * Because of storage in tpnames, dirtempunlink() can unlink the file later.
1500 * Return a pointer to the pathname created.
1501 * If ISWORKFILE is 1, put it into the working file's directory;
1502 * if 0, put the unique file in RCSfile's directory.
1503 */
1504{
1505	register char *tp, *np;
1506	register size_t dl;
1507	register struct buf *bn;
1508	register char const *name = isworkfile ? workname : RCSname;
1509
1510	dl = basefilename(name) - name;
1511	bn = &dirtpname[newRCSdirtp_index + isworkfile];
1512	bufalloc(bn,
1513#		if has_mktemp
1514			dl + 9
1515#		else
1516			strlen(name) + 3
1517#		endif
1518	);
1519	bufscpy(bn, name);
1520	np = tp = bn->string;
1521	tp += dl;
1522	*tp++ = '_';
1523	*tp++ = '0'+isworkfile;
1524	catchints();
1525#	if has_mktemp
1526		VOID strcpy(tp, "XXXXXX");
1527		if (!mktemp(np) || !*np)
1528		    faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
1529			(int)dl, name, '0'+isworkfile
1530		    );
1531#	else
1532		/*
1533		 * Posix 1003.1-1990 has no reliable way
1534		 * to create a unique file in a named directory.
1535		 * We fudge here.  If the filename is abcde,
1536		 * the temp filename is _Ncde where N is a digit.
1537		 */
1538		name += dl;
1539		if (*name) name++;
1540		if (*name) name++;
1541		VOID strcpy(tp, name);
1542#	endif
1543	dirtpmaker[newRCSdirtp_index + isworkfile] = real;
1544	return np;
1545}
1546
1547	void
1548dirtempunlink()
1549/* Clean up makedirtemp() files.  May be invoked by signal handler. */
1550{
1551	register int i;
1552	enum maker m;
1553
1554	for (i = DIRTEMPNAMES;  0 <= --i;  )
1555	    if ((m = dirtpmaker[i]) != notmade) {
1556		if (m == effective)
1557		    seteid();
1558		VOID un_link(dirtpname[i].string);
1559		if (m == effective)
1560		    setrid();
1561		dirtpmaker[i] = notmade;
1562	    }
1563}
1564
1565
1566	int
1567#if has_prototypes
1568chnamemod(
1569	FILE **fromp, char const *from, char const *to,
1570	int set_mode, mode_t mode, time_t mtime
1571)
1572  /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1573#else
1574  chnamemod(fromp, from, to, set_mode, mode, mtime)
1575	FILE **fromp; char const *from,*to;
1576	int set_mode; mode_t mode; time_t mtime;
1577#endif
1578/*
1579 * Rename a file (with stream pointer *FROMP) from FROM to TO.
1580 * FROM already exists.
1581 * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
1582 * If MTIME is not -1, change its mtime to MTIME before renaming.
1583 * Close and clear *FROMP before renaming it.
1584 * Unlink TO if it already exists.
1585 * Return -1 on error (setting errno), 0 otherwise.
1586 */
1587{
1588	mode_t mode_while_renaming = mode;
1589	int fchmod_set_mode = 0;
1590
1591#	if bad_a_rename || bad_NFS_rename
1592	    struct stat st;
1593	    if (bad_NFS_rename  ||  (bad_a_rename && set_mode <= 0)) {
1594		if (fstat(fileno(*fromp), &st) != 0)
1595		    return -1;
1596		if (bad_a_rename && set_mode <= 0)
1597		    mode = st.st_mode;
1598	    }
1599#	endif
1600
1601#	if bad_a_rename
1602		/*
1603		* There's a short window of inconsistency
1604		* during which the lock file is writable.
1605		*/
1606		mode_while_renaming = mode|S_IWUSR;
1607		if (mode != mode_while_renaming)
1608		    set_mode = 1;
1609#	endif
1610
1611#	if has_fchmod
1612	    if (0<set_mode  &&  fchmod(fileno(*fromp),mode_while_renaming) == 0)
1613		fchmod_set_mode = set_mode;
1614#	endif
1615	/* If bad_chmod_close, we must close before chmod.  */
1616	Ozclose(fromp);
1617	if (fchmod_set_mode<set_mode  &&  chmod(from, mode_while_renaming) != 0)
1618	    return -1;
1619
1620	if (setmtime(from, mtime) != 0)
1621		return -1;
1622
1623#	if !has_rename || bad_b_rename
1624		/*
1625		* There's a short window of inconsistency
1626		* during which TO does not exist.
1627		*/
1628		if (un_link(to) != 0  &&  errno != ENOENT)
1629			return -1;
1630#	endif
1631
1632#	if has_rename
1633	    if (rename(from,to) != 0  &&  !(has_NFS && errno==ENOENT))
1634		return -1;
1635#	else
1636	    if (do_link(from,to) != 0  ||  un_link(from) != 0)
1637		return -1;
1638#	endif
1639
1640#	if bad_NFS_rename
1641	{
1642	    /*
1643	    * Check whether the rename falsely reported success.
1644	    * A race condition can occur between the rename and the stat.
1645	    */
1646	    struct stat tostat;
1647	    if (stat(to, &tostat) != 0)
1648		return -1;
1649	    if (! same_file(st, tostat, 0)) {
1650		errno = EIO;
1651		return -1;
1652	    }
1653	}
1654#	endif
1655
1656#	if bad_a_rename
1657	    if (0 < set_mode  &&  chmod(to, mode) != 0)
1658		return -1;
1659#	endif
1660
1661	return 0;
1662}
1663
1664	int
1665setmtime(file, mtime)
1666	char const *file;
1667	time_t mtime;
1668/* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1.  */
1669{
1670	static struct utimbuf amtime; /* static so unused fields are zero */
1671	if (mtime == -1)
1672		return 0;
1673	amtime.actime = now();
1674	amtime.modtime = mtime;
1675	return utime(file, &amtime);
1676}
1677
1678
1679
1680	int
1681findlock(delete, target)
1682	int delete;
1683	struct hshentry **target;
1684/*
1685 * Find the first lock held by caller and return a pointer
1686 * to the locked delta; also removes the lock if DELETE.
1687 * If one lock, put it into *TARGET.
1688 * Return 0 for no locks, 1 for one, 2 for two or more.
1689 */
1690{
1691	register struct rcslock *next, **trail, **found;
1692
1693	found = 0;
1694	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
1695		if (strcmp(getcaller(), next->login)  ==  0) {
1696			if (found) {
1697				rcserror("multiple revisions locked by %s; please specify one", getcaller());
1698				return 2;
1699			}
1700			found = trail;
1701		}
1702	if (!found)
1703		return 0;
1704	next = *found;
1705	*target = next->delta;
1706	if (delete) {
1707		next->delta->lockedby = 0;
1708		*found = next->nextlock;
1709	}
1710	return 1;
1711}
1712
1713	int
1714addlock(delta, verbose)
1715	struct hshentry * delta;
1716	int verbose;
1717/*
1718 * Add a lock held by caller to DELTA and yield 1 if successful.
1719 * Print an error message if verbose and yield -1 if no lock is added because
1720 * DELTA is locked by somebody other than caller.
1721 * Return 0 if the caller already holds the lock.
1722 */
1723{
1724	register struct rcslock *next;
1725
1726	for (next = Locks;  next;  next = next->nextlock)
1727		if (cmpnum(delta->num, next->delta->num) == 0)
1728			if (strcmp(getcaller(), next->login) == 0)
1729				return 0;
1730			else {
1731				if (verbose)
1732				  rcserror("Revision %s is already locked by %s.",
1733					delta->num, next->login
1734				  );
1735				return -1;
1736			}
1737	next = ftalloc(struct rcslock);
1738	delta->lockedby = next->login = getcaller();
1739	next->delta = delta;
1740	next->nextlock = Locks;
1741	Locks = next;
1742	return 1;
1743}
1744
1745
1746	int
1747addsymbol(num, name, rebind)
1748	char const *num, *name;
1749	int rebind;
1750/*
1751 * Associate with revision NUM the new symbolic NAME.
1752 * If NAME already exists and REBIND is set, associate NAME with NUM;
1753 * otherwise, print an error message and return false;
1754 * Return -1 if unsuccessful, 0 if no change, 1 if change.
1755 */
1756{
1757	register struct assoc *next;
1758
1759	for (next = Symbols;  next;  next = next->nextassoc)
1760		if (strcmp(name, next->symbol)  ==  0)
1761			if (strcmp(next->num,num) == 0)
1762				return 0;
1763			else if (rebind) {
1764				next->num = num;
1765				return 1;
1766			} else {
1767				rcserror("symbolic name %s already bound to %s",
1768					name, next->num
1769				);
1770				return -1;
1771			}
1772	next = ftalloc(struct assoc);
1773	next->symbol = name;
1774	next->num = num;
1775	next->nextassoc = Symbols;
1776	Symbols = next;
1777	return 1;
1778}
1779
1780
1781
1782	char const *
1783getcaller()
1784/* Get the caller's login name.  */
1785{
1786#	if has_setuid
1787		return getusername(euid()!=ruid());
1788#	else
1789		return getusername(false);
1790#	endif
1791}
1792
1793
1794	int
1795checkaccesslist()
1796/*
1797 * Return true if caller is the superuser, the owner of the
1798 * file, the access list is empty, or caller is on the access list.
1799 * Otherwise, print an error message and return false.
1800 */
1801{
1802	register struct access const *next;
1803
1804	if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
1805		return true;
1806
1807	next = AccessList;
1808	do {
1809		if (strcmp(getcaller(), next->login)  ==  0)
1810			return true;
1811	} while ((next = next->nextaccess));
1812
1813	rcserror("user %s not on the access list", getcaller());
1814	return false;
1815}
1816
1817
1818	int
1819dorewrite(lockflag, changed)
1820	int lockflag, changed;
1821/*
1822 * Do nothing if LOCKFLAG is zero.
1823 * Prepare to rewrite an RCS file if CHANGED is positive.
1824 * Stop rewriting if CHANGED is zero, because there won't be any changes.
1825 * Fail if CHANGED is negative.
1826 * Return 0 on success, -1 on failure.
1827 */
1828{
1829	int r = 0, e;
1830
1831	if (lockflag)
1832		if (changed) {
1833			if (changed < 0)
1834				return -1;
1835			putadmin();
1836			puttree(Head, frewrite);
1837			aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
1838			foutptr = frewrite;
1839		} else {
1840#			if bad_creat0
1841				int nr = !!frewrite, ne = 0;
1842#			endif
1843			ORCSclose();
1844			seteid();
1845			ignoreints();
1846#			if bad_creat0
1847				if (nr) {
1848					nr = un_link(newRCSname);
1849					ne = errno;
1850					keepdirtemp(newRCSname);
1851				}
1852#			endif
1853			r = un_link(lockname);
1854			e = errno;
1855			keepdirtemp(lockname);
1856			restoreints();
1857			setrid();
1858			if (r != 0)
1859				enerror(e, lockname);
1860#			if bad_creat0
1861				if (nr != 0) {
1862					enerror(ne, newRCSname);
1863					r = -1;
1864				}
1865#			endif
1866		}
1867	return r;
1868}
1869
1870	int
1871donerewrite(changed, newRCStime)
1872	int changed;
1873	time_t newRCStime;
1874/*
1875 * Finish rewriting an RCS file if CHANGED is nonzero.
1876 * Set its mode if CHANGED is positive.
1877 * Set its modification time to NEWRCSTIME unless it is -1.
1878 * Return 0 on success, -1 on failure.
1879 */
1880{
1881	int r = 0, e = 0;
1882#	if bad_creat0
1883		int lr, le;
1884#	endif
1885
1886	if (changed && !nerror) {
1887		if (finptr) {
1888			fastcopy(finptr, frewrite);
1889			Izclose(&finptr);
1890		}
1891		if (1 < RCSstat.st_nlink)
1892			rcswarn("breaking hard link");
1893		aflush(frewrite);
1894		seteid();
1895		ignoreints();
1896		r = chnamemod(
1897			&frewrite, newRCSname, RCSname, changed,
1898			RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
1899			newRCStime
1900		);
1901		e = errno;
1902		keepdirtemp(newRCSname);
1903#		if bad_creat0
1904			lr = un_link(lockname);
1905			le = errno;
1906			keepdirtemp(lockname);
1907#		endif
1908		restoreints();
1909		setrid();
1910		if (r != 0) {
1911			enerror(e, RCSname);
1912			error("saved in %s", newRCSname);
1913		}
1914#		if bad_creat0
1915			if (lr != 0) {
1916				enerror(le, lockname);
1917				r = -1;
1918			}
1919#		endif
1920	}
1921	return r;
1922}
1923
1924	void
1925ORCSclose()
1926{
1927	if (0 <= fdlock) {
1928		if (close(fdlock) != 0)
1929			efaterror(lockname);
1930		fdlock = -1;
1931	}
1932	Ozclose(&frewrite);
1933}
1934
1935	void
1936ORCSerror()
1937/*
1938* Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
1939* Do not report errors, since this may loop.  This is needed only because
1940* some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
1941* some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
1942* This isn't a completely reliable away to work around brain-damaged hosts,
1943* because of the gap between actual file opening and setting frewrite etc.,
1944* but it's better than nothing.
1945*/
1946{
1947	if (0 <= fdlock)
1948		VOID close(fdlock);
1949	if (frewrite)
1950		/* Avoid fclose, since stdio may not be reentrant.  */
1951		VOID close(fileno(frewrite));
1952}
1953