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