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