xref: /dragonfly/gnu/usr.bin/rcs/lib/rcsedit.c (revision 6e285212)
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 
14 This file is part of RCS.
15 
16 RCS is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation; either version 2, or (at your option)
19 any later version.
20 
21 RCS is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 GNU General Public License for more details.
25 
26 You should have received a copy of the GNU General Public License
27 along with RCS; see the file COPYING.
28 If not, write to the Free Software Foundation,
29 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
30 
31 Report problems and direct all questions to:
32 
33     rcs-bugs@cs.purdue.edu
34 
35 */
36 
37 /*
38  * $FreeBSD: src/gnu/usr.bin/rcs/lib/rcsedit.c,v 1.11.2.1 2001/05/12 10:29:42 kris Exp $
39  * $DragonFly: src/gnu/usr.bin/rcs/lib/rcsedit.c,v 1.2 2003/06/17 04:25:47 dillon Exp $
40  *
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 
207 libId(editId, "$DragonFly: src/gnu/usr.bin/rcs/lib/rcsedit.c,v 1.2 2003/06/17 04:25:47 dillon Exp $")
208 
209 static void editEndsPrematurely P((void)) exiting;
210 static void editLineNumberOverflow P((void)) exiting;
211 static void escape_string P((FILE*,char const*));
212 static void keyreplace P((enum markers,struct hshentry const*,int,RILE*,FILE*,int));
213 
214 FILE *fcopy;		 /* result file descriptor			    */
215 char const *resultname;	 /* result pathname				    */
216 int 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
221 static long editline; /* edit line counter; #lines before cursor   */
222 static 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 
232 enum maker {notmade, real, effective};
233 static struct buf dirtpname[DIRTEMPNAMES];	/* unlink these when done */
234 static 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
241 un_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
278 do_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
301 editEndsPrematurely()
302 {
303 	fatserror("edit script ends prematurely");
304 }
305 
306 	static void
307 editLineNumberOverflow()
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
320 movelines(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 
339 static void deletelines P((long,long));
340 static void finisheditline P((RILE*,FILE*,Iptr_type,struct hshentry const*));
341 static void insertline P((long,Iptr_type));
342 static 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  */
353 static Iptr_type *line;
354 static size_t gap, gapsize, linelim;
355 
356 	static void
357 insertline(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
383 deletelines(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
400 snapshotline(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
413 snapshotedit(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
425 finisheditline(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
437 finishedit(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 *
469 fopen_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
480 openfcopy(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
496 swapeditfiles(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
513 snapshotedit(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
523 finishedit(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
558 copylines(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
601 xpandstring(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
615 copystring()
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 {	register 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
661 enterstring()
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
726 edit_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
851 expandline(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 	register 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
992 escape_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 
1011 char const ciklog[ciklogsize] = "checked in with -k by ";
1012 
1013 	static void
1014 keyreplace(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 LocalId:
1056 	    case Header:
1057 	    case CVSHeader:
1058 		if (marker == Id || RCSv < VERSION(4) ||
1059 		    (marker == LocalId && LocalIdMode == Id))
1060 			escape_string(out, basefilename(RCSname));
1061 		else if (marker == CVSHeader ||
1062 		    (marker == LocalId && LocalIdMode == CVSHeader))
1063 			escape_string(out, getfullCVSname());
1064 		else
1065 			escape_string(out, getfullRCSname());
1066 		aprintf(out, " %s %s %s %s",
1067 			delta->num,
1068 			date2str(date, datebuf),
1069 			delta->author,
1070 			  RCSv==VERSION(3) && delta->lockedby ? "Locked"
1071 			: delta->state
1072 		);
1073 		if (delta->lockedby)
1074 		    if (VERSION(5) <= RCSv) {
1075 			if (locker_expansion || exp==KEYVALLOCK_EXPAND)
1076 			    aprintf(out, " %s", delta->lockedby);
1077 		    } else if (RCSv == VERSION(4))
1078 			aprintf(out, " Locker: %s", delta->lockedby);
1079                 break;
1080 	    case Locker:
1081 		if (delta->lockedby)
1082 		    if (
1083 				locker_expansion
1084 			||	exp == KEYVALLOCK_EXPAND
1085 			||	RCSv <= VERSION(4)
1086 		    )
1087 			aputs(delta->lockedby, out);
1088                 break;
1089 	    case Log:
1090 	    case RCSfile:
1091 		escape_string(out, basefilename(RCSname));
1092                 break;
1093 	    case Name:
1094 		if (delta->name)
1095 			aputs(delta->name, out);
1096 		break;
1097 	    case Revision:
1098 		aputs(delta->num, out);
1099                 break;
1100 	    case Source:
1101 		escape_string(out, getfullRCSname());
1102                 break;
1103 	    case State:
1104 		aputs(delta->state, out);
1105                 break;
1106 	    default:
1107 		break;
1108 	    }
1109 	    if (exp != VAL_EXPAND)
1110 		afputc(' ', out);
1111 	}
1112 	if (exp != VAL_EXPAND)
1113 	    afputc(KDELIM, out);
1114 
1115 	if (marker == Log   &&  dolog) {
1116 		struct buf leader;
1117 
1118 		sp = delta->log.string;
1119 		ls = delta->log.size;
1120 		if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
1121 			return;
1122 		bufautobegin(&leader);
1123 		if (RCSversion < VERSION(5)) {
1124 		    cp = Comment.string;
1125 		    cs = Comment.size;
1126 		} else {
1127 		    int kdelim_found = 0;
1128 		    Ioffset_type chars_read = Itell(infile);
1129 		    declarecache;
1130 		    setupcache(infile); cache(infile);
1131 
1132 		    c = 0; /* Pacify `gcc -Wall'.  */
1133 
1134 		    /*
1135 		    * Back up to the start of the current input line,
1136 		    * setting CS to the number of characters before `$Log'.
1137 		    */
1138 		    cs = 0;
1139 		    for (;;) {
1140 			if (!--chars_read)
1141 			    goto done_backing_up;
1142 			cacheunget_(infile, c)
1143 			if (c == '\n')
1144 			    break;
1145 			if (c == SDELIM  &&  delimstuffed) {
1146 			    if (!--chars_read)
1147 				break;
1148 			    cacheunget_(infile, c)
1149 			    if (c != SDELIM) {
1150 				cacheget_(c)
1151 				break;
1152 			    }
1153 			}
1154 			cs += kdelim_found;
1155 			kdelim_found |= c==KDELIM;
1156 		    }
1157 		    cacheget_(c)
1158 		  done_backing_up:;
1159 
1160 		    /* Copy characters before `$Log' into LEADER.  */
1161 		    bufalloc(&leader, cs);
1162 		    cp = leader.string;
1163 		    for (cw = 0;  cw < cs;  cw++) {
1164 			leader.string[cw] = c;
1165 			if (c == SDELIM  &&  delimstuffed)
1166 			    cacheget_(c)
1167 			cacheget_(c)
1168 		    }
1169 
1170 		    /* Convert traditional C or Pascal leader to ` *'.  */
1171 		    for (cw = 0;  cw < cs;  cw++)
1172 			if (ctab[(unsigned char) cp[cw]] != SPACE)
1173 			    break;
1174 		    if (
1175 			cw+1 < cs
1176 			&&  cp[cw+1] == '*'
1177 			&&  (cp[cw] == '/'  ||  cp[cw] == '(')
1178 		    ) {
1179 			size_t i = cw+1;
1180 			for (;;)
1181 			    if (++i == cs) {
1182 				warn(
1183 				    "`%c* $Log' is obsolescent; use ` * $Log'.",
1184 				    cp[cw]
1185 				);
1186 				leader.string[cw] = ' ';
1187 				break;
1188 			    } else if (ctab[(unsigned char) cp[i]] != SPACE)
1189 				break;
1190 		    }
1191 
1192 		    /* Skip `$Log ... $' string.  */
1193 		    do {
1194 			cacheget_(c)
1195 		    } while (c != KDELIM);
1196 		    uncache(infile);
1197 		}
1198 		afputc('\n', out);
1199 		awrite(cp, cs, out);
1200 		sp1 = date2str(date, datebuf);
1201 		if (VERSION(5) <= RCSv) {
1202 		    aprintf(out, "Revision %s  %s  %s",
1203 			delta->num, sp1, delta->author
1204 		    );
1205 		} else {
1206 		    /* oddity: 2 spaces between date and time, not 1 as usual */
1207 		    sp1 = strchr(sp1, ' ');
1208 		    aprintf(out, "Revision %s  %.*s %s  %s",
1209 			delta->num, (int)(sp1-datebuf), datebuf, sp1,
1210 			delta->author
1211 		    );
1212 		}
1213 		/* Do not include state: it may change and is not updated.  */
1214 		cw = cs;
1215 		if (VERSION(5) <= RCSv)
1216 		    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
1217 			continue;
1218 		for (;;) {
1219 		    afputc('\n', out);
1220 		    awrite(cp, cw, out);
1221 		    if (!ls)
1222 			break;
1223 		    --ls;
1224 		    c = *sp++;
1225 		    if (c != '\n') {
1226 			awrite(cp+cw, cs-cw, out);
1227 			do {
1228 			    afputc(c,out);
1229 			    if (!ls)
1230 				break;
1231 			    --ls;
1232 			    c = *sp++;
1233 			} while (c != '\n');
1234 		    }
1235 		}
1236 		bufautoend(&leader);
1237 	}
1238 }
1239 
1240 #if has_readlink
1241 	static int resolve_symlink P((struct buf*));
1242 	static int
1243 resolve_symlink(L)
1244 	struct buf *L;
1245 /*
1246  * If L is a symbolic link, resolve it to the name that it points to.
1247  * If unsuccessful, set errno and yield -1.
1248  * If it points to an existing file, yield 1.
1249  * Otherwise, set errno=ENOENT and yield 0.
1250  */
1251 {
1252 	char *b, a[SIZEABLE_PATH];
1253 	int e;
1254 	size_t s;
1255 	ssize_t r;
1256 	struct buf bigbuf;
1257 	int linkcount = MAXSYMLINKS;
1258 
1259 	b = a;
1260 	s = sizeof(a);
1261 	bufautobegin(&bigbuf);
1262 	while ((r = readlink(L->string,b,s))  !=  -1)
1263 	    if (r == s) {
1264 		bufalloc(&bigbuf, s<<1);
1265 		b = bigbuf.string;
1266 		s = bigbuf.size;
1267 	    } else if (!linkcount--) {
1268 #		ifndef ELOOP
1269 		    /*
1270 		    * Some pedantic Posix 1003.1-1990 hosts have readlink
1271 		    * but not ELOOP.  Approximate ELOOP with EMLINK.
1272 		    */
1273 #		    define ELOOP EMLINK
1274 #		endif
1275 		errno = ELOOP;
1276 		return -1;
1277 	    } else {
1278 		/* Splice symbolic link into L.  */
1279 		b[r] = '\0';
1280 		L->string[
1281 		  ROOTPATH(b)  ?  0  :  basefilename(L->string) - L->string
1282 		] = '\0';
1283 		bufscat(L, b);
1284 	    }
1285 	e = errno;
1286 	bufautoend(&bigbuf);
1287 	errno = e;
1288 	switch (e) {
1289 	    case readlink_isreg_errno: return 1;
1290 	    case ENOENT: return 0;
1291 	    default: return -1;
1292 	}
1293 }
1294 #endif
1295 
1296 	RILE *
1297 rcswriteopen(RCSbuf, status, mustread)
1298 	struct buf *RCSbuf;
1299 	struct stat *status;
1300 	int mustread;
1301 /*
1302  * Create the lock file corresponding to RCSBUF.
1303  * Then try to open RCSBUF for reading and yield its RILE* descriptor.
1304  * Put its status into *STATUS too.
1305  * MUSTREAD is true if the file must already exist, too.
1306  * If all goes well, discard any previously acquired locks,
1307  * and set fdlock to the file descriptor of the RCS lockfile.
1308  */
1309 {
1310 	register char *tp;
1311 	register char const *sp, *RCSpath, *x;
1312 	RILE *f;
1313 	size_t l;
1314 	int e, exists, fdesc, fdescSafer, r, waslocked;
1315 	struct buf *dirt;
1316 	struct stat statbuf;
1317 
1318 	waslocked  =  0 <= fdlock;
1319 	exists =
1320 #		if has_readlink
1321 			resolve_symlink(RCSbuf);
1322 #		else
1323 			    stat(RCSbuf->string, &statbuf) == 0  ?  1
1324 			:   errno==ENOENT ? 0 : -1;
1325 #		endif
1326 	if (exists < (mustread|waslocked))
1327 		/*
1328 		 * There's an unusual problem with the RCS file;
1329 		 * or the RCS file doesn't exist,
1330 		 * and we must read or we already have a lock elsewhere.
1331 		 */
1332 		return 0;
1333 
1334 	RCSpath = RCSbuf->string;
1335 	sp = basefilename(RCSpath);
1336 	l = sp - RCSpath;
1337 	dirt = &dirtpname[waslocked];
1338 	bufscpy(dirt, RCSpath);
1339 	tp = dirt->string + l;
1340 	x = rcssuffix(RCSpath);
1341 #	if has_readlink
1342 	    if (!x) {
1343 		error("symbolic link to non RCS file `%s'", RCSpath);
1344 		errno = EINVAL;
1345 		return 0;
1346 	    }
1347 #	endif
1348 	if (*sp == *x) {
1349 		error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
1350 		errno = EINVAL;
1351 		return 0;
1352 	}
1353 	/* Create a lock filename that is a function of the RCS filename.  */
1354 	if (*x) {
1355 		/*
1356 		 * The suffix is nonempty.
1357 		 * The lock filename is the first char of of the suffix,
1358 		 * followed by the RCS filename with last char removed.  E.g.:
1359 		 *	foo,v	RCS filename with suffix ,v
1360 		 *	,foo,	lock filename
1361 		 */
1362 		*tp++ = *x;
1363 		while (*sp)
1364 			*tp++ = *sp++;
1365 		*--tp = 0;
1366 	} else {
1367 		/*
1368 		 * The suffix is empty.
1369 		 * The lock filename is the RCS filename
1370 		 * with last char replaced by '_'.
1371 		 */
1372 		while ((*tp++ = *sp++))
1373 			continue;
1374 		tp -= 2;
1375 		if (*tp == '_') {
1376 			error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
1377 			errno = EINVAL;
1378 			return 0;
1379 		}
1380 		*tp = '_';
1381 	}
1382 
1383 	sp = dirt->string;
1384 
1385 	f = 0;
1386 
1387 	/*
1388 	* good news:
1389 	*	open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
1390 	*	is atomic according to Posix 1003.1-1990.
1391 	* bad news:
1392 	*	NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
1393 	* good news:
1394 	*	(O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity
1395 	*	even with NFS.
1396 	* bad news:
1397 	*	If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
1398 	*	guarantee atomicity.
1399 	* good news:
1400 	*	Root-over-the-wire NFS access is rare for security reasons.
1401 	*	This bug has never been reported in practice with RCS.
1402 	* So we don't worry about this bug.
1403 	*
1404 	* An even rarer NFS bug can occur when clients retry requests.
1405 	* This can happen in the usual case of NFS over UDP.
1406 	* Suppose client A releases a lock by renaming ",f," to "f,v" at
1407 	* about the same time that client B obtains a lock by creating ",f,",
1408 	* and suppose A's first rename request is delayed, so A reissues it.
1409 	* The sequence of events might be:
1410 	*	A sends rename(",f,", "f,v")
1411 	*	B sends create(",f,")
1412 	*	A sends retry of rename(",f,", "f,v")
1413 	*	server receives, does, and acknowledges A's first rename()
1414 	*	A receives acknowledgment, and its RCS program exits
1415 	*	server receives, does, and acknowledges B's create()
1416 	*	server receives, does, and acknowledges A's retry of rename()
1417 	* This not only wrongly deletes B's lock, it removes the RCS file!
1418 	* Most NFS implementations have idempotency caches that usually prevent
1419 	* this scenario, but such caches are finite and can be overrun.
1420 	* This problem afflicts not only RCS, which uses open() and rename()
1421 	* to get and release locks; it also afflicts the traditional
1422 	* Unix method of using link() and unlink() to get and release locks,
1423 	* and the less traditional method of using mkdir() and rmdir().
1424 	* There is no easy workaround.
1425 	* Any new method based on lockf() seemingly would be incompatible with
1426 	* the old methods; besides, lockf() is notoriously buggy under NFS.
1427 	* Since this problem afflicts scads of Unix programs, but is so rare
1428 	* that nobody seems to be worried about it, we won't worry either.
1429 	*/
1430 #	if !open_can_creat
1431 #		define create(f) creat(f, OPEN_CREAT_READONLY)
1432 #	else
1433 #		define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
1434 #	endif
1435 
1436 	catchints();
1437 	ignoreints();
1438 
1439 	/*
1440 	 * Create a lock file for an RCS file.  This should be atomic, i.e.
1441 	 * if two processes try it simultaneously, at most one should succeed.
1442 	 */
1443 	seteid();
1444 	fdesc = create(sp);
1445 	fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr.  */
1446 	e = errno;
1447 	setrid();
1448 
1449 	if (0 <= fdesc)
1450 		dirtpmaker[0] = effective;
1451 
1452 	if (fdescSafer < 0) {
1453 		if (e == EACCES  &&  stat(sp,&statbuf) == 0)
1454 			/* The RCS file is busy.  */
1455 			e = EEXIST;
1456 	} else {
1457 		e = ENOENT;
1458 		if (exists) {
1459 		    f = Iopen(RCSpath, FOPEN_RB, status);
1460 		    e = errno;
1461 		    if (f && waslocked) {
1462 			/* Discard the previous lock in favor of this one.  */
1463 			ORCSclose();
1464 			seteid();
1465 			r = un_link(lockname);
1466 			e = errno;
1467 			setrid();
1468 			if (r != 0)
1469 			    enfaterror(e, lockname);
1470 			bufscpy(&dirtpname[lockdirtp_index], sp);
1471 		    }
1472 		}
1473 		fdlock = fdescSafer;
1474 	}
1475 
1476 	restoreints();
1477 
1478 	errno = e;
1479 	return f;
1480 }
1481 
1482 	void
1483 keepdirtemp(name)
1484 	char const *name;
1485 /* Do not unlink name, either because it's not there any more,
1486  * or because it has already been unlinked.
1487  */
1488 {
1489 	register int i;
1490 	for (i=DIRTEMPNAMES; 0<=--i; )
1491 		if (dirtpname[i].string == name) {
1492 			dirtpmaker[i] = notmade;
1493 			return;
1494 		}
1495 	faterror("keepdirtemp");
1496 }
1497 
1498 	char const *
1499 makedirtemp(isworkfile)
1500 	int isworkfile;
1501 /*
1502  * Create a unique pathname and store it into dirtpname.
1503  * Because of storage in tpnames, dirtempunlink() can unlink the file later.
1504  * Return a pointer to the pathname created.
1505  * If ISWORKFILE is 1, put it into the working file's directory;
1506  * if 0, put the unique file in RCSfile's directory.
1507  */
1508 {
1509 	register char *tp, *np;
1510 	register size_t dl;
1511 	register struct buf *bn;
1512 	register char const *name = isworkfile ? workname : RCSname;
1513 #	if has_mktemp
1514 	int fd;
1515 #	endif
1516 
1517 	dl = basefilename(name) - name;
1518 	bn = &dirtpname[newRCSdirtp_index + isworkfile];
1519 	bufalloc(bn,
1520 #		if has_mktemp
1521 			dl + 9
1522 #		else
1523 			strlen(name) + 3
1524 #		endif
1525 	);
1526 	bufscpy(bn, name);
1527 	np = tp = bn->string;
1528 	tp += dl;
1529 	*tp++ = '_';
1530 	*tp++ = '0'+isworkfile;
1531 	catchints();
1532 #	if has_mktemp
1533 		VOID strcpy(tp, "XXXXXX");
1534 		fd = mkstemp(np);
1535 		if (fd < 0 || !*np)
1536 		    faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
1537 			(int)dl, name, '0'+isworkfile
1538 		    );
1539 		close(fd);
1540 #	else
1541 		/*
1542 		 * Posix 1003.1-1990 has no reliable way
1543 		 * to create a unique file in a named directory.
1544 		 * We fudge here.  If the filename is abcde,
1545 		 * the temp filename is _Ncde where N is a digit.
1546 		 */
1547 		name += dl;
1548 		if (*name) name++;
1549 		if (*name) name++;
1550 		VOID strcpy(tp, name);
1551 #	endif
1552 	dirtpmaker[newRCSdirtp_index + isworkfile] = real;
1553 	return np;
1554 }
1555 
1556 	void
1557 dirtempunlink()
1558 /* Clean up makedirtemp() files.  May be invoked by signal handler. */
1559 {
1560 	register int i;
1561 	enum maker m;
1562 
1563 	for (i = DIRTEMPNAMES;  0 <= --i;  )
1564 	    if ((m = dirtpmaker[i]) != notmade) {
1565 		if (m == effective)
1566 		    seteid();
1567 		VOID un_link(dirtpname[i].string);
1568 		if (m == effective)
1569 		    setrid();
1570 		dirtpmaker[i] = notmade;
1571 	    }
1572 }
1573 
1574 
1575 	int
1576 #if has_prototypes
1577 chnamemod(
1578 	FILE **fromp, char const *from, char const *to,
1579 	int set_mode, mode_t mode, time_t mtime
1580 )
1581   /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1582 #else
1583   chnamemod(fromp, from, to, set_mode, mode, mtime)
1584 	FILE **fromp; char const *from,*to;
1585 	int set_mode; mode_t mode; time_t mtime;
1586 #endif
1587 /*
1588  * Rename a file (with stream pointer *FROMP) from FROM to TO.
1589  * FROM already exists.
1590  * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
1591  * If MTIME is not -1, change its mtime to MTIME before renaming.
1592  * Close and clear *FROMP before renaming it.
1593  * Unlink TO if it already exists.
1594  * Return -1 on error (setting errno), 0 otherwise.
1595  */
1596 {
1597 	mode_t mode_while_renaming = mode;
1598 	int fchmod_set_mode = 0;
1599 
1600 #	if bad_a_rename || bad_NFS_rename
1601 	    struct stat st;
1602 	    if (bad_NFS_rename  ||  (bad_a_rename && set_mode <= 0)) {
1603 		if (fstat(fileno(*fromp), &st) != 0)
1604 		    return -1;
1605 		if (bad_a_rename && set_mode <= 0)
1606 		    mode = st.st_mode;
1607 	    }
1608 #	endif
1609 
1610 #	if bad_a_rename
1611 		/*
1612 		* There's a short window of inconsistency
1613 		* during which the lock file is writable.
1614 		*/
1615 		mode_while_renaming = mode|S_IWUSR;
1616 		if (mode != mode_while_renaming)
1617 		    set_mode = 1;
1618 #	endif
1619 
1620 #	if has_fchmod
1621 	    if (0<set_mode  &&  fchmod(fileno(*fromp),mode_while_renaming) == 0)
1622 		fchmod_set_mode = set_mode;
1623 #	endif
1624 	/* If bad_chmod_close, we must close before chmod.  */
1625 	Ozclose(fromp);
1626 	if (fchmod_set_mode<set_mode  &&  chmod(from, mode_while_renaming) != 0)
1627 	    return -1;
1628 
1629 	if (setmtime(from, mtime) != 0)
1630 		return -1;
1631 
1632 #	if !has_rename || bad_b_rename
1633 		/*
1634 		* There's a short window of inconsistency
1635 		* during which TO does not exist.
1636 		*/
1637 		if (un_link(to) != 0  &&  errno != ENOENT)
1638 			return -1;
1639 #	endif
1640 
1641 #	if has_rename
1642 	    if (rename(from,to) != 0  &&  !(has_NFS && errno==ENOENT))
1643 		return -1;
1644 #	else
1645 	    if (do_link(from,to) != 0  ||  un_link(from) != 0)
1646 		return -1;
1647 #	endif
1648 
1649 #	if bad_NFS_rename
1650 	{
1651 	    /*
1652 	    * Check whether the rename falsely reported success.
1653 	    * A race condition can occur between the rename and the stat.
1654 	    */
1655 	    struct stat tostat;
1656 	    if (stat(to, &tostat) != 0)
1657 		return -1;
1658 	    if (! same_file(st, tostat, 0)) {
1659 		errno = EIO;
1660 		return -1;
1661 	    }
1662 	}
1663 #	endif
1664 
1665 #	if bad_a_rename
1666 	    if (0 < set_mode  &&  chmod(to, mode) != 0)
1667 		return -1;
1668 #	endif
1669 
1670 	return 0;
1671 }
1672 
1673 	int
1674 setmtime(file, mtime)
1675 	char const *file;
1676 	time_t mtime;
1677 /* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1.  */
1678 {
1679 	static struct utimbuf amtime; /* static so unused fields are zero */
1680 	if (mtime == -1)
1681 		return 0;
1682 	amtime.actime = now();
1683 	amtime.modtime = mtime;
1684 	return utime(file, &amtime);
1685 }
1686 
1687 
1688 
1689 	int
1690 findlock(delete, target)
1691 	int delete;
1692 	struct hshentry **target;
1693 /*
1694  * Find the first lock held by caller and return a pointer
1695  * to the locked delta; also removes the lock if DELETE.
1696  * If one lock, put it into *TARGET.
1697  * Return 0 for no locks, 1 for one, 2 for two or more.
1698  */
1699 {
1700 	register struct rcslock *next, **trail, **found;
1701 
1702 	found = 0;
1703 	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
1704 		if (strcmp(getcaller(), next->login)  ==  0) {
1705 			if (found) {
1706 				rcserror("multiple revisions locked by %s; please specify one", getcaller());
1707 				return 2;
1708 			}
1709 			found = trail;
1710 		}
1711 	if (!found)
1712 		return 0;
1713 	next = *found;
1714 	*target = next->delta;
1715 	if (delete) {
1716 		next->delta->lockedby = 0;
1717 		*found = next->nextlock;
1718 	}
1719 	return 1;
1720 }
1721 
1722 	int
1723 addlock(delta, verbose)
1724 	struct hshentry * delta;
1725 	int verbose;
1726 /*
1727  * Add a lock held by caller to DELTA and yield 1 if successful.
1728  * Print an error message if verbose and yield -1 if no lock is added because
1729  * DELTA is locked by somebody other than caller.
1730  * Return 0 if the caller already holds the lock.
1731  */
1732 {
1733 	register struct rcslock *next;
1734 
1735 	for (next = Locks;  next;  next = next->nextlock)
1736 		if (cmpnum(delta->num, next->delta->num) == 0)
1737 			if (strcmp(getcaller(), next->login) == 0)
1738 				return 0;
1739 			else {
1740 				if (verbose)
1741 				  rcserror("Revision %s is already locked by %s.",
1742 					delta->num, next->login
1743 				  );
1744 				return -1;
1745 			}
1746 	next = ftalloc(struct rcslock);
1747 	delta->lockedby = next->login = getcaller();
1748 	next->delta = delta;
1749 	next->nextlock = Locks;
1750 	Locks = next;
1751 	return 1;
1752 }
1753 
1754 
1755 	int
1756 addsymbol(num, name, rebind)
1757 	char const *num, *name;
1758 	int rebind;
1759 /*
1760  * Associate with revision NUM the new symbolic NAME.
1761  * If NAME already exists and REBIND is set, associate NAME with NUM;
1762  * otherwise, print an error message and return false;
1763  * Return -1 if unsuccessful, 0 if no change, 1 if change.
1764  */
1765 {
1766 	register struct assoc *next;
1767 
1768 	for (next = Symbols;  next;  next = next->nextassoc)
1769 		if (strcmp(name, next->symbol)  ==  0)
1770 			if (strcmp(next->num,num) == 0)
1771 				return 0;
1772 			else if (rebind) {
1773 				next->num = num;
1774 				return 1;
1775 			} else {
1776 				rcserror("symbolic name %s already bound to %s",
1777 					name, next->num
1778 				);
1779 				return -1;
1780 			}
1781 	next = ftalloc(struct assoc);
1782 	next->symbol = name;
1783 	next->num = num;
1784 	next->nextassoc = Symbols;
1785 	Symbols = next;
1786 	return 1;
1787 }
1788 
1789 
1790 
1791 	char const *
1792 getcaller()
1793 /* Get the caller's login name.  */
1794 {
1795 #	if has_setuid
1796 		return getusername(euid()!=ruid());
1797 #	else
1798 		return getusername(false);
1799 #	endif
1800 }
1801 
1802 
1803 	int
1804 checkaccesslist()
1805 /*
1806  * Return true if caller is the superuser, the owner of the
1807  * file, the access list is empty, or caller is on the access list.
1808  * Otherwise, print an error message and return false.
1809  */
1810 {
1811 	register struct access const *next;
1812 
1813 	if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
1814 		return true;
1815 
1816 	next = AccessList;
1817 	do {
1818 		if (strcmp(getcaller(), next->login)  ==  0)
1819 			return true;
1820 	} while ((next = next->nextaccess));
1821 
1822 	rcserror("user %s not on the access list", getcaller());
1823 	return false;
1824 }
1825 
1826 
1827 	int
1828 dorewrite(lockflag, changed)
1829 	int lockflag, changed;
1830 /*
1831  * Do nothing if LOCKFLAG is zero.
1832  * Prepare to rewrite an RCS file if CHANGED is positive.
1833  * Stop rewriting if CHANGED is zero, because there won't be any changes.
1834  * Fail if CHANGED is negative.
1835  * Return 0 on success, -1 on failure.
1836  */
1837 {
1838 	int r = 0, e;
1839 
1840 	if (lockflag)
1841 		if (changed) {
1842 			if (changed < 0)
1843 				return -1;
1844 			putadmin();
1845 			puttree(Head, frewrite);
1846 			aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
1847 			foutptr = frewrite;
1848 		} else {
1849 #			if bad_creat0
1850 				int nr = !!frewrite, ne = 0;
1851 #			endif
1852 			ORCSclose();
1853 			seteid();
1854 			ignoreints();
1855 #			if bad_creat0
1856 				if (nr) {
1857 					nr = un_link(newRCSname);
1858 					ne = errno;
1859 					keepdirtemp(newRCSname);
1860 				}
1861 #			endif
1862 			r = un_link(lockname);
1863 			e = errno;
1864 			keepdirtemp(lockname);
1865 			restoreints();
1866 			setrid();
1867 			if (r != 0)
1868 				enerror(e, lockname);
1869 #			if bad_creat0
1870 				if (nr != 0) {
1871 					enerror(ne, newRCSname);
1872 					r = -1;
1873 				}
1874 #			endif
1875 		}
1876 	return r;
1877 }
1878 
1879 	int
1880 donerewrite(changed, newRCStime)
1881 	int changed;
1882 	time_t newRCStime;
1883 /*
1884  * Finish rewriting an RCS file if CHANGED is nonzero.
1885  * Set its mode if CHANGED is positive.
1886  * Set its modification time to NEWRCSTIME unless it is -1.
1887  * Return 0 on success, -1 on failure.
1888  */
1889 {
1890 	int r = 0, e = 0;
1891 #	if bad_creat0
1892 		int lr, le;
1893 #	endif
1894 
1895 	if (changed && !nerror) {
1896 		if (finptr) {
1897 			fastcopy(finptr, frewrite);
1898 			Izclose(&finptr);
1899 		}
1900 		if (1 < RCSstat.st_nlink)
1901 			rcswarn("breaking hard link");
1902 		aflush(frewrite);
1903 		seteid();
1904 		ignoreints();
1905 		r = chnamemod(
1906 			&frewrite, newRCSname, RCSname, changed,
1907 			RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
1908 			newRCStime
1909 		);
1910 		e = errno;
1911 		keepdirtemp(newRCSname);
1912 #		if bad_creat0
1913 			lr = un_link(lockname);
1914 			le = errno;
1915 			keepdirtemp(lockname);
1916 #		endif
1917 		restoreints();
1918 		setrid();
1919 		if (r != 0) {
1920 			enerror(e, RCSname);
1921 			error("saved in %s", newRCSname);
1922 		}
1923 #		if bad_creat0
1924 			if (lr != 0) {
1925 				enerror(le, lockname);
1926 				r = -1;
1927 			}
1928 #		endif
1929 	}
1930 	return r;
1931 }
1932 
1933 	void
1934 ORCSclose()
1935 {
1936 	if (0 <= fdlock) {
1937 		if (close(fdlock) != 0)
1938 			efaterror(lockname);
1939 		fdlock = -1;
1940 	}
1941 	Ozclose(&frewrite);
1942 }
1943 
1944 	void
1945 ORCSerror()
1946 /*
1947 * Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
1948 * Do not report errors, since this may loop.  This is needed only because
1949 * some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
1950 * some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
1951 * This isn't a completely reliable away to work around brain-damaged hosts,
1952 * because of the gap between actual file opening and setting frewrite etc.,
1953 * but it's better than nothing.
1954 */
1955 {
1956 	if (0 <= fdlock)
1957 		VOID close(fdlock);
1958 	if (frewrite)
1959 		/* Avoid fclose, since stdio may not be reentrant.  */
1960 		VOID close(fileno(frewrite));
1961 }
1962