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