xref: /dragonfly/gnu/usr.bin/rcs/lib/rcsedit.c (revision 86d7f5d3)
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
un_link(s)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
do_link(s,t)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
editEndsPrematurely()301 editEndsPrematurely()
302 {
303 	fatserror("edit script ends prematurely");
304 }
305 
306 	static void
editLineNumberOverflow()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
movelines(s1,s2,n)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
insertline(n,l)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
deletelines(n,nlines)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
snapshotline(f,l)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
snapshotedit(f)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
finisheditline(fin,fout,l,delta)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
finishedit(delta,outfile,done)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 *
fopen_update_truncate(name)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
openfcopy(f)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
swapeditfiles(outfile)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
snapshotedit(f)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
finishedit(delta,outfile,done)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
copylines(upto,delta)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
xpandstring(delta)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
copystring()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 int c;
623 	declarecache;
624 	register FILE *frew, *fcop;
625 	register int amidline;
626 	register RILE *fin;
627 
628 	fin = finptr;
629 	setupcache(fin); cache(fin);
630 	frew = foutptr;
631 	fcop = fcopy;
632 	amidline = false;
633 	for (;;) {
634 		GETC_(frew,c)
635 		switch (c) {
636 		    case '\n':
637 			++editline;
638 			++rcsline;
639 			amidline = false;
640 			break;
641 		    case SDELIM:
642 			GETC_(frew,c)
643 			if (c != SDELIM) {
644 				/* end of string */
645 				nextc = c;
646 				editline += amidline;
647 				uncache(fin);
648 				return;
649 			}
650 			/* fall into */
651 		    default:
652 			amidline = true;
653 			break;
654                 }
655 		aputc_(c,fcop)
656         }
657 }
658 
659 
660 	void
enterstring()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
edit_string()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
expandline(infile,outfile,delta,delimstuffed,frewfile,dolog)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 int c;
868 	declarecache;
869 	register FILE *out, *frew;
870 	register char * tp;
871 	register int e, ds, r;
872 	char const *tlim;
873 	static struct buf keyval;
874         enum markers matchresult;
875 
876 	setupcache(infile); cache(infile);
877 	out = outfile;
878 	frew = frewfile;
879 	ds = delimstuffed;
880 	bufalloc(&keyval, keylength+3);
881 	e = 0;
882 	r = -1;
883 
884         for (;;) {
885 	    if (ds)
886 		GETC_(frew, c)
887 	    else
888 		cachegeteof_(c, goto uncache_exit;)
889 	    for (;;) {
890 		switch (c) {
891 		    case SDELIM:
892 			if (ds) {
893 			    GETC_(frew, c)
894 			    if (c != SDELIM) {
895                                 /* end of string */
896                                 nextc=c;
897 				goto uncache_exit;
898 			    }
899 			}
900 			/* fall into */
901 		    default:
902 			aputc_(c,out)
903 			r = 0;
904 			break;
905 
906 		    case '\n':
907 			rcsline += ds;
908 			aputc_(c,out)
909 			r = 2;
910 			goto uncache_exit;
911 
912 		    case KDELIM:
913 			r = 0;
914                         /* check for keyword */
915                         /* first, copy a long enough string into keystring */
916 			tp = keyval.string;
917 			*tp++ = KDELIM;
918 			for (;;) {
919 			    if (ds)
920 				GETC_(frew, c)
921 			    else
922 				cachegeteof_(c, goto keystring_eof;)
923 			    if (tp <= &keyval.string[keylength])
924 				switch (ctab[c]) {
925 				    case LETTER: case Letter:
926 					*tp++ = c;
927 					continue;
928 				    default:
929 					break;
930 				}
931 			    break;
932                         }
933 			*tp++ = c; *tp = '\0';
934 			matchresult = trymatch(keyval.string+1);
935 			if (matchresult==Nomatch) {
936 				tp[-1] = 0;
937 				aputs(keyval.string, out);
938 				continue;   /* last c handled properly */
939 			}
940 
941 			/* Now we have a keyword terminated with a K/VDELIM */
942 			if (c==VDELIM) {
943 			      /* try to find closing KDELIM, and replace value */
944 			      tlim = keyval.string + keyval.size;
945 			      for (;;) {
946 				      if (ds)
947 					GETC_(frew, c)
948 				      else
949 					cachegeteof_(c, goto keystring_eof;)
950 				      if (c=='\n' || c==KDELIM)
951 					break;
952 				      *tp++ =c;
953 				      if (tlim <= tp)
954 					  tp = bufenlarge(&keyval, &tlim);
955 				      if (c==SDELIM && ds) { /*skip next SDELIM */
956 						GETC_(frew, c)
957 						if (c != SDELIM) {
958 							/* end of string before closing KDELIM or newline */
959 							nextc = c;
960 							goto keystring_eof;
961 						}
962 				      }
963 			      }
964 			      if (c!=KDELIM) {
965 				    /* couldn't find closing KDELIM -- give up */
966 				    *tp = 0;
967 				    aputs(keyval.string, out);
968 				    continue;   /* last c handled properly */
969 			      }
970 			}
971 			/* now put out the new keyword value */
972 			uncache(infile);
973 			keyreplace(matchresult, delta, ds, infile, out, dolog);
974 			cache(infile);
975 			e = 1;
976 			break;
977                 }
978 		break;
979 	    }
980         }
981 
982     keystring_eof:
983 	*tp = 0;
984 	aputs(keyval.string, out);
985     uncache_exit:
986 	uncache(infile);
987 	return r + e;
988 }
989 
990 
991 	static void
escape_string(out,s)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
keyreplace(marker,delta,delimstuffed,infile,out,dolog)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 		}
1080                 break;
1081 	    case Locker:
1082 		if (delta->lockedby)
1083 		    if (
1084 				locker_expansion
1085 			||	exp == KEYVALLOCK_EXPAND
1086 			||	RCSv <= VERSION(4)
1087 		    )
1088 			aputs(delta->lockedby, out);
1089                 break;
1090 	    case Log:
1091 	    case RCSfile:
1092 		escape_string(out, basefilename(RCSname));
1093                 break;
1094 	    case Name:
1095 		if (delta->name)
1096 			aputs(delta->name, out);
1097 		break;
1098 	    case Revision:
1099 		aputs(delta->num, out);
1100                 break;
1101 	    case Source:
1102 		escape_string(out, getfullRCSname());
1103                 break;
1104 	    case State:
1105 		aputs(delta->state, out);
1106                 break;
1107 	    default:
1108 		break;
1109 	    }
1110 	    if (exp != VAL_EXPAND)
1111 		afputc(' ', out);
1112 	}
1113 	if (exp != VAL_EXPAND)
1114 	    afputc(KDELIM, out);
1115 
1116 	if (marker == Log   &&  dolog) {
1117 		struct buf leader;
1118 
1119 		sp = delta->log.string;
1120 		ls = delta->log.size;
1121 		if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
1122 			return;
1123 		bufautobegin(&leader);
1124 		if (RCSversion < VERSION(5)) {
1125 		    cp = Comment.string;
1126 		    cs = Comment.size;
1127 		} else {
1128 		    int kdelim_found = 0;
1129 		    Ioffset_type chars_read = Itell(infile);
1130 		    declarecache;
1131 		    setupcache(infile); cache(infile);
1132 
1133 		    c = 0; /* Pacify `gcc -Wall'.  */
1134 
1135 		    /*
1136 		    * Back up to the start of the current input line,
1137 		    * setting CS to the number of characters before `$Log'.
1138 		    */
1139 		    cs = 0;
1140 		    for (;;) {
1141 			if (!--chars_read)
1142 			    goto done_backing_up;
1143 			cacheunget_(infile, c)
1144 			if (c == '\n')
1145 			    break;
1146 			if (c == SDELIM  &&  delimstuffed) {
1147 			    if (!--chars_read)
1148 				break;
1149 			    cacheunget_(infile, c)
1150 			    if (c != SDELIM) {
1151 				cacheget_(c)
1152 				break;
1153 			    }
1154 			}
1155 			cs += kdelim_found;
1156 			kdelim_found |= c==KDELIM;
1157 		    }
1158 		    cacheget_(c)
1159 		  done_backing_up:;
1160 
1161 		    /* Copy characters before `$Log' into LEADER.  */
1162 		    bufalloc(&leader, cs);
1163 		    cp = leader.string;
1164 		    for (cw = 0;  cw < cs;  cw++) {
1165 			leader.string[cw] = c;
1166 			if (c == SDELIM  &&  delimstuffed) {
1167 			    cacheget_(c)
1168 			}
1169 			cacheget_(c)
1170 		    }
1171 
1172 		    /* Convert traditional C or Pascal leader to ` *'.  */
1173 		    for (cw = 0;  cw < cs;  cw++)
1174 			if (ctab[(unsigned char) cp[cw]] != SPACE)
1175 			    break;
1176 		    if (
1177 			cw+1 < cs
1178 			&&  cp[cw+1] == '*'
1179 			&&  (cp[cw] == '/'  ||  cp[cw] == '(')
1180 		    ) {
1181 			size_t i = cw+1;
1182 			for (;;)
1183 			    if (++i == cs) {
1184 				warn(
1185 				    "`%c* $Log' is obsolescent; use ` * $Log'.",
1186 				    cp[cw]
1187 				);
1188 				leader.string[cw] = ' ';
1189 				break;
1190 			    } else if (ctab[(unsigned char) cp[i]] != SPACE)
1191 				break;
1192 		    }
1193 
1194 		    /* Skip `$Log ... $' string.  */
1195 		    do {
1196 			cacheget_(c)
1197 		    } while (c != KDELIM);
1198 		    uncache(infile);
1199 		}
1200 		afputc('\n', out);
1201 		awrite(cp, cs, out);
1202 		sp1 = date2str(date, datebuf);
1203 		if (VERSION(5) <= RCSv) {
1204 		    aprintf(out, "Revision %s  %s  %s",
1205 			delta->num, sp1, delta->author
1206 		    );
1207 		} else {
1208 		    /* oddity: 2 spaces between date and time, not 1 as usual */
1209 		    sp1 = strchr(sp1, ' ');
1210 		    aprintf(out, "Revision %s  %.*s %s  %s",
1211 			delta->num, (int)(sp1-datebuf), datebuf, sp1,
1212 			delta->author
1213 		    );
1214 		}
1215 		/* Do not include state: it may change and is not updated.  */
1216 		cw = cs;
1217 		if (VERSION(5) <= RCSv)
1218 		    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
1219 			continue;
1220 		for (;;) {
1221 		    afputc('\n', out);
1222 		    awrite(cp, cw, out);
1223 		    if (!ls)
1224 			break;
1225 		    --ls;
1226 		    c = *sp++;
1227 		    if (c != '\n') {
1228 			awrite(cp+cw, cs-cw, out);
1229 			do {
1230 			    afputc(c,out);
1231 			    if (!ls)
1232 				break;
1233 			    --ls;
1234 			    c = *sp++;
1235 			} while (c != '\n');
1236 		    }
1237 		}
1238 		bufautoend(&leader);
1239 	}
1240 }
1241 
1242 #if has_readlink
1243 	static int resolve_symlink P((struct buf*));
1244 	static int
resolve_symlink(L)1245 resolve_symlink(L)
1246 	struct buf *L;
1247 /*
1248  * If L is a symbolic link, resolve it to the name that it points to.
1249  * If unsuccessful, set errno and yield -1.
1250  * If it points to an existing file, yield 1.
1251  * Otherwise, set errno=ENOENT and yield 0.
1252  */
1253 {
1254 	char *b, a[SIZEABLE_PATH];
1255 	int e;
1256 	size_t s;
1257 	ssize_t r;
1258 	struct buf bigbuf;
1259 	int linkcount = MAXSYMLINKS;
1260 
1261 	b = a;
1262 	s = sizeof(a);
1263 	bufautobegin(&bigbuf);
1264 	while ((r = readlink(L->string,b,s))  !=  -1)
1265 	    if (r == s) {
1266 		bufalloc(&bigbuf, s<<1);
1267 		b = bigbuf.string;
1268 		s = bigbuf.size;
1269 	    } else if (!linkcount--) {
1270 #		ifndef ELOOP
1271 		    /*
1272 		    * Some pedantic Posix 1003.1-1990 hosts have readlink
1273 		    * but not ELOOP.  Approximate ELOOP with EMLINK.
1274 		    */
1275 #		    define ELOOP EMLINK
1276 #		endif
1277 		errno = ELOOP;
1278 		return -1;
1279 	    } else {
1280 		/* Splice symbolic link into L.  */
1281 		b[r] = '\0';
1282 		L->string[
1283 		  ROOTPATH(b)  ?  0  :  basefilename(L->string) - L->string
1284 		] = '\0';
1285 		bufscat(L, b);
1286 	    }
1287 	e = errno;
1288 	bufautoend(&bigbuf);
1289 	errno = e;
1290 	switch (e) {
1291 	    case readlink_isreg_errno: return 1;
1292 	    case ENOENT: return 0;
1293 	    default: return -1;
1294 	}
1295 }
1296 #endif
1297 
1298 	RILE *
rcswriteopen(RCSbuf,status,mustread)1299 rcswriteopen(RCSbuf, status, mustread)
1300 	struct buf *RCSbuf;
1301 	struct stat *status;
1302 	int mustread;
1303 /*
1304  * Create the lock file corresponding to RCSBUF.
1305  * Then try to open RCSBUF for reading and yield its RILE* descriptor.
1306  * Put its status into *STATUS too.
1307  * MUSTREAD is true if the file must already exist, too.
1308  * If all goes well, discard any previously acquired locks,
1309  * and set fdlock to the file descriptor of the RCS lockfile.
1310  */
1311 {
1312 	register char *tp;
1313 	register char const *sp, *RCSpath, *x;
1314 	RILE *f;
1315 	size_t l;
1316 	int e, exists, fdesc, fdescSafer, r, waslocked;
1317 	struct buf *dirt;
1318 	struct stat statbuf;
1319 
1320 	waslocked  =  0 <= fdlock;
1321 	exists =
1322 #		if has_readlink
1323 			resolve_symlink(RCSbuf);
1324 #		else
1325 			    stat(RCSbuf->string, &statbuf) == 0  ?  1
1326 			:   errno==ENOENT ? 0 : -1;
1327 #		endif
1328 	if (exists < (mustread|waslocked))
1329 		/*
1330 		 * There's an unusual problem with the RCS file;
1331 		 * or the RCS file doesn't exist,
1332 		 * and we must read or we already have a lock elsewhere.
1333 		 */
1334 		return 0;
1335 
1336 	RCSpath = RCSbuf->string;
1337 	sp = basefilename(RCSpath);
1338 	l = sp - RCSpath;
1339 	dirt = &dirtpname[waslocked];
1340 	bufscpy(dirt, RCSpath);
1341 	tp = dirt->string + l;
1342 	x = rcssuffix(RCSpath);
1343 #	if has_readlink
1344 	    if (!x) {
1345 		error("symbolic link to non RCS file `%s'", RCSpath);
1346 		errno = EINVAL;
1347 		return 0;
1348 	    }
1349 #	endif
1350 	if (*sp == *x) {
1351 		error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
1352 		errno = EINVAL;
1353 		return 0;
1354 	}
1355 	/* Create a lock filename that is a function of the RCS filename.  */
1356 	if (*x) {
1357 		/*
1358 		 * The suffix is nonempty.
1359 		 * The lock filename is the first char of of the suffix,
1360 		 * followed by the RCS filename with last char removed.  E.g.:
1361 		 *	foo,v	RCS filename with suffix ,v
1362 		 *	,foo,	lock filename
1363 		 */
1364 		*tp++ = *x;
1365 		while (*sp)
1366 			*tp++ = *sp++;
1367 		*--tp = 0;
1368 	} else {
1369 		/*
1370 		 * The suffix is empty.
1371 		 * The lock filename is the RCS filename
1372 		 * with last char replaced by '_'.
1373 		 */
1374 		while ((*tp++ = *sp++))
1375 			continue;
1376 		tp -= 2;
1377 		if (*tp == '_') {
1378 			error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
1379 			errno = EINVAL;
1380 			return 0;
1381 		}
1382 		*tp = '_';
1383 	}
1384 
1385 	sp = dirt->string;
1386 
1387 	f = 0;
1388 
1389 	/*
1390 	* good news:
1391 	*	open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
1392 	*	is atomic according to Posix 1003.1-1990.
1393 	* bad news:
1394 	*	NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
1395 	* good news:
1396 	*	(O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity
1397 	*	even with NFS.
1398 	* bad news:
1399 	*	If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
1400 	*	guarantee atomicity.
1401 	* good news:
1402 	*	Root-over-the-wire NFS access is rare for security reasons.
1403 	*	This bug has never been reported in practice with RCS.
1404 	* So we don't worry about this bug.
1405 	*
1406 	* An even rarer NFS bug can occur when clients retry requests.
1407 	* This can happen in the usual case of NFS over UDP.
1408 	* Suppose client A releases a lock by renaming ",f," to "f,v" at
1409 	* about the same time that client B obtains a lock by creating ",f,",
1410 	* and suppose A's first rename request is delayed, so A reissues it.
1411 	* The sequence of events might be:
1412 	*	A sends rename(",f,", "f,v")
1413 	*	B sends create(",f,")
1414 	*	A sends retry of rename(",f,", "f,v")
1415 	*	server receives, does, and acknowledges A's first rename()
1416 	*	A receives acknowledgment, and its RCS program exits
1417 	*	server receives, does, and acknowledges B's create()
1418 	*	server receives, does, and acknowledges A's retry of rename()
1419 	* This not only wrongly deletes B's lock, it removes the RCS file!
1420 	* Most NFS implementations have idempotency caches that usually prevent
1421 	* this scenario, but such caches are finite and can be overrun.
1422 	* This problem afflicts not only RCS, which uses open() and rename()
1423 	* to get and release locks; it also afflicts the traditional
1424 	* Unix method of using link() and unlink() to get and release locks,
1425 	* and the less traditional method of using mkdir() and rmdir().
1426 	* There is no easy workaround.
1427 	* Any new method based on lockf() seemingly would be incompatible with
1428 	* the old methods; besides, lockf() is notoriously buggy under NFS.
1429 	* Since this problem afflicts scads of Unix programs, but is so rare
1430 	* that nobody seems to be worried about it, we won't worry either.
1431 	*/
1432 #	if !open_can_creat
1433 #		define create(f) creat(f, OPEN_CREAT_READONLY)
1434 #	else
1435 #		define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
1436 #	endif
1437 
1438 	catchints();
1439 	ignoreints();
1440 
1441 	/*
1442 	 * Create a lock file for an RCS file.  This should be atomic, i.e.
1443 	 * if two processes try it simultaneously, at most one should succeed.
1444 	 */
1445 	seteid();
1446 	fdesc = create(sp);
1447 	fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr.  */
1448 	e = errno;
1449 	setrid();
1450 
1451 	if (0 <= fdesc)
1452 		dirtpmaker[0] = effective;
1453 
1454 	if (fdescSafer < 0) {
1455 		if (e == EACCES  &&  stat(sp,&statbuf) == 0)
1456 			/* The RCS file is busy.  */
1457 			e = EEXIST;
1458 	} else {
1459 		e = ENOENT;
1460 		if (exists) {
1461 		    f = Iopen(RCSpath, FOPEN_RB, status);
1462 		    e = errno;
1463 		    if (f && waslocked) {
1464 			/* Discard the previous lock in favor of this one.  */
1465 			ORCSclose();
1466 			seteid();
1467 			r = un_link(lockname);
1468 			e = errno;
1469 			setrid();
1470 			if (r != 0)
1471 			    enfaterror(e, lockname);
1472 			bufscpy(&dirtpname[lockdirtp_index], sp);
1473 		    }
1474 		}
1475 		fdlock = fdescSafer;
1476 	}
1477 
1478 	restoreints();
1479 
1480 	errno = e;
1481 	return f;
1482 }
1483 
1484 	void
keepdirtemp(name)1485 keepdirtemp(name)
1486 	char const *name;
1487 /* Do not unlink name, either because it's not there any more,
1488  * or because it has already been unlinked.
1489  */
1490 {
1491 	register int i;
1492 	for (i=DIRTEMPNAMES; 0<=--i; )
1493 		if (dirtpname[i].string == name) {
1494 			dirtpmaker[i] = notmade;
1495 			return;
1496 		}
1497 	faterror("keepdirtemp");
1498 }
1499 
1500 	char const *
makedirtemp(isworkfile)1501 makedirtemp(isworkfile)
1502 	int isworkfile;
1503 /*
1504  * Create a unique pathname and store it into dirtpname.
1505  * Because of storage in tpnames, dirtempunlink() can unlink the file later.
1506  * Return a pointer to the pathname created.
1507  * If ISWORKFILE is 1, put it into the working file's directory;
1508  * if 0, put the unique file in RCSfile's directory.
1509  */
1510 {
1511 	register char *tp, *np;
1512 	register size_t dl;
1513 	register struct buf *bn;
1514 	register char const *name = isworkfile ? workname : RCSname;
1515 #	if has_mktemp
1516 	int fd;
1517 #	endif
1518 
1519 	dl = basefilename(name) - name;
1520 	bn = &dirtpname[newRCSdirtp_index + isworkfile];
1521 	bufalloc(bn,
1522 #		if has_mktemp
1523 			dl + 9
1524 #		else
1525 			strlen(name) + 3
1526 #		endif
1527 	);
1528 	bufscpy(bn, name);
1529 	np = tp = bn->string;
1530 	tp += dl;
1531 	*tp++ = '_';
1532 	*tp++ = '0'+isworkfile;
1533 	catchints();
1534 #	if has_mktemp
1535 		VOID strcpy(tp, "XXXXXX");
1536 		fd = mkstemp(np);
1537 		if (fd < 0 || !*np)
1538 		    faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
1539 			(int)dl, name, '0'+isworkfile
1540 		    );
1541 		close(fd);
1542 #	else
1543 		/*
1544 		 * Posix 1003.1-1990 has no reliable way
1545 		 * to create a unique file in a named directory.
1546 		 * We fudge here.  If the filename is abcde,
1547 		 * the temp filename is _Ncde where N is a digit.
1548 		 */
1549 		name += dl;
1550 		if (*name) name++;
1551 		if (*name) name++;
1552 		VOID strcpy(tp, name);
1553 #	endif
1554 	dirtpmaker[newRCSdirtp_index + isworkfile] = real;
1555 	return np;
1556 }
1557 
1558 	void
dirtempunlink()1559 dirtempunlink()
1560 /* Clean up makedirtemp() files.  May be invoked by signal handler. */
1561 {
1562 	register int i;
1563 	enum maker m;
1564 
1565 	for (i = DIRTEMPNAMES;  0 <= --i;  )
1566 	    if ((m = dirtpmaker[i]) != notmade) {
1567 		if (m == effective)
1568 		    seteid();
1569 		VOID un_link(dirtpname[i].string);
1570 		if (m == effective)
1571 		    setrid();
1572 		dirtpmaker[i] = notmade;
1573 	    }
1574 }
1575 
1576 
1577 	int
1578 #if has_prototypes
chnamemod(FILE ** fromp,char const * from,char const * to,int set_mode,mode_t mode,time_t mtime)1579 chnamemod(
1580 	FILE **fromp, char const *from, char const *to,
1581 	int set_mode, mode_t mode, time_t mtime
1582 )
1583   /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1584 #else
1585   chnamemod(fromp, from, to, set_mode, mode, mtime)
1586 	FILE **fromp; char const *from,*to;
1587 	int set_mode; mode_t mode; time_t mtime;
1588 #endif
1589 /*
1590  * Rename a file (with stream pointer *FROMP) from FROM to TO.
1591  * FROM already exists.
1592  * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
1593  * If MTIME is not -1, change its mtime to MTIME before renaming.
1594  * Close and clear *FROMP before renaming it.
1595  * Unlink TO if it already exists.
1596  * Return -1 on error (setting errno), 0 otherwise.
1597  */
1598 {
1599 	mode_t mode_while_renaming = mode;
1600 	int fchmod_set_mode = 0;
1601 
1602 #	if bad_a_rename || bad_NFS_rename
1603 	    struct stat st;
1604 	    if (bad_NFS_rename  ||  (bad_a_rename && set_mode <= 0)) {
1605 		if (fstat(fileno(*fromp), &st) != 0)
1606 		    return -1;
1607 		if (bad_a_rename && set_mode <= 0)
1608 		    mode = st.st_mode;
1609 	    }
1610 #	endif
1611 
1612 #	if bad_a_rename
1613 		/*
1614 		* There's a short window of inconsistency
1615 		* during which the lock file is writable.
1616 		*/
1617 		mode_while_renaming = mode|S_IWUSR;
1618 		if (mode != mode_while_renaming)
1619 		    set_mode = 1;
1620 #	endif
1621 
1622 #	if has_fchmod
1623 	    if (0<set_mode  &&  fchmod(fileno(*fromp),mode_while_renaming) == 0)
1624 		fchmod_set_mode = set_mode;
1625 #	endif
1626 	/* If bad_chmod_close, we must close before chmod.  */
1627 	Ozclose(fromp);
1628 	if (fchmod_set_mode<set_mode  &&  chmod(from, mode_while_renaming) != 0)
1629 	    return -1;
1630 
1631 	if (setmtime(from, mtime) != 0)
1632 		return -1;
1633 
1634 #	if !has_rename || bad_b_rename
1635 		/*
1636 		* There's a short window of inconsistency
1637 		* during which TO does not exist.
1638 		*/
1639 		if (un_link(to) != 0  &&  errno != ENOENT)
1640 			return -1;
1641 #	endif
1642 
1643 #	if has_rename
1644 	    if (rename(from,to) != 0  &&  !(has_NFS && errno==ENOENT))
1645 		return -1;
1646 #	else
1647 	    if (do_link(from,to) != 0  ||  un_link(from) != 0)
1648 		return -1;
1649 #	endif
1650 
1651 #	if bad_NFS_rename
1652 	{
1653 	    /*
1654 	    * Check whether the rename falsely reported success.
1655 	    * A race condition can occur between the rename and the stat.
1656 	    */
1657 	    struct stat tostat;
1658 	    if (stat(to, &tostat) != 0)
1659 		return -1;
1660 	    if (! same_file(st, tostat, 0)) {
1661 		errno = EIO;
1662 		return -1;
1663 	    }
1664 	}
1665 #	endif
1666 
1667 #	if bad_a_rename
1668 	    if (0 < set_mode  &&  chmod(to, mode) != 0)
1669 		return -1;
1670 #	endif
1671 
1672 	return 0;
1673 }
1674 
1675 	int
setmtime(file,mtime)1676 setmtime(file, mtime)
1677 	char const *file;
1678 	time_t mtime;
1679 /* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1.  */
1680 {
1681 	static struct utimbuf amtime; /* static so unused fields are zero */
1682 	if (mtime == -1)
1683 		return 0;
1684 	amtime.actime = now();
1685 	amtime.modtime = mtime;
1686 	return utime(file, &amtime);
1687 }
1688 
1689 
1690 
1691 	int
findlock(delete,target)1692 findlock(delete, target)
1693 	int delete;
1694 	struct hshentry **target;
1695 /*
1696  * Find the first lock held by caller and return a pointer
1697  * to the locked delta; also removes the lock if DELETE.
1698  * If one lock, put it into *TARGET.
1699  * Return 0 for no locks, 1 for one, 2 for two or more.
1700  */
1701 {
1702 	register struct rcslock *next, **trail, **found;
1703 
1704 	found = 0;
1705 	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
1706 		if (strcmp(getcaller(), next->login)  ==  0) {
1707 			if (found) {
1708 				rcserror("multiple revisions locked by %s; please specify one", getcaller());
1709 				return 2;
1710 			}
1711 			found = trail;
1712 		}
1713 	if (!found)
1714 		return 0;
1715 	next = *found;
1716 	*target = next->delta;
1717 	if (delete) {
1718 		next->delta->lockedby = 0;
1719 		*found = next->nextlock;
1720 	}
1721 	return 1;
1722 }
1723 
1724 	int
addlock(delta,verbose)1725 addlock(delta, verbose)
1726 	struct hshentry * delta;
1727 	int verbose;
1728 /*
1729  * Add a lock held by caller to DELTA and yield 1 if successful.
1730  * Print an error message if verbose and yield -1 if no lock is added because
1731  * DELTA is locked by somebody other than caller.
1732  * Return 0 if the caller already holds the lock.
1733  */
1734 {
1735 	register struct rcslock *next;
1736 
1737 	for (next = Locks;  next;  next = next->nextlock)
1738 		if (cmpnum(delta->num, next->delta->num) == 0) {
1739 			if (strcmp(getcaller(), next->login) == 0)
1740 				return 0;
1741 			else {
1742 				if (verbose)
1743 				  rcserror("Revision %s is already locked by %s.",
1744 					delta->num, next->login
1745 				  );
1746 				return -1;
1747 			}
1748 		}
1749 	next = ftalloc(struct rcslock);
1750 	delta->lockedby = next->login = getcaller();
1751 	next->delta = delta;
1752 	next->nextlock = Locks;
1753 	Locks = next;
1754 	return 1;
1755 }
1756 
1757 
1758 	int
addsymbol(num,name,rebind)1759 addsymbol(num, name, rebind)
1760 	char const *num, *name;
1761 	int rebind;
1762 /*
1763  * Associate with revision NUM the new symbolic NAME.
1764  * If NAME already exists and REBIND is set, associate NAME with NUM;
1765  * otherwise, print an error message and return false;
1766  * Return -1 if unsuccessful, 0 if no change, 1 if change.
1767  */
1768 {
1769 	register struct assoc *next;
1770 
1771 	for (next = Symbols;  next;  next = next->nextassoc)
1772 		if (strcmp(name, next->symbol)  ==  0) {
1773 			if (strcmp(next->num,num) == 0)
1774 				return 0;
1775 			else if (rebind) {
1776 				next->num = num;
1777 				return 1;
1778 			} else {
1779 				rcserror("symbolic name %s already bound to %s",
1780 					name, next->num
1781 				);
1782 				return -1;
1783 			}
1784 		}
1785 	next = ftalloc(struct assoc);
1786 	next->symbol = name;
1787 	next->num = num;
1788 	next->nextassoc = Symbols;
1789 	Symbols = next;
1790 	return 1;
1791 }
1792 
1793 
1794 
1795 	char const *
getcaller()1796 getcaller()
1797 /* Get the caller's login name.  */
1798 {
1799 #	if has_setuid
1800 		return getusername(euid()!=ruid());
1801 #	else
1802 		return getusername(false);
1803 #	endif
1804 }
1805 
1806 
1807 	int
checkaccesslist()1808 checkaccesslist()
1809 /*
1810  * Return true if caller is the superuser, the owner of the
1811  * file, the access list is empty, or caller is on the access list.
1812  * Otherwise, print an error message and return false.
1813  */
1814 {
1815 	register struct access const *next;
1816 
1817 	if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
1818 		return true;
1819 
1820 	next = AccessList;
1821 	do {
1822 		if (strcmp(getcaller(), next->login)  ==  0)
1823 			return true;
1824 	} while ((next = next->nextaccess));
1825 
1826 	rcserror("user %s not on the access list", getcaller());
1827 	return false;
1828 }
1829 
1830 
1831 	int
dorewrite(lockflag,changed)1832 dorewrite(lockflag, changed)
1833 	int lockflag, changed;
1834 /*
1835  * Do nothing if LOCKFLAG is zero.
1836  * Prepare to rewrite an RCS file if CHANGED is positive.
1837  * Stop rewriting if CHANGED is zero, because there won't be any changes.
1838  * Fail if CHANGED is negative.
1839  * Return 0 on success, -1 on failure.
1840  */
1841 {
1842 	int r = 0, e;
1843 
1844 	if (lockflag) {
1845 		if (changed) {
1846 			if (changed < 0)
1847 				return -1;
1848 			putadmin();
1849 			puttree(Head, frewrite);
1850 			aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
1851 			foutptr = frewrite;
1852 		} else {
1853 #			if bad_creat0
1854 				int nr = !!frewrite, ne = 0;
1855 #			endif
1856 			ORCSclose();
1857 			seteid();
1858 			ignoreints();
1859 #			if bad_creat0
1860 				if (nr) {
1861 					nr = un_link(newRCSname);
1862 					ne = errno;
1863 					keepdirtemp(newRCSname);
1864 				}
1865 #			endif
1866 			r = un_link(lockname);
1867 			e = errno;
1868 			keepdirtemp(lockname);
1869 			restoreints();
1870 			setrid();
1871 			if (r != 0)
1872 				enerror(e, lockname);
1873 #			if bad_creat0
1874 				if (nr != 0) {
1875 					enerror(ne, newRCSname);
1876 					r = -1;
1877 				}
1878 #			endif
1879 		}
1880 	}
1881 	return r;
1882 }
1883 
1884 	int
donerewrite(changed,newRCStime)1885 donerewrite(changed, newRCStime)
1886 	int changed;
1887 	time_t newRCStime;
1888 /*
1889  * Finish rewriting an RCS file if CHANGED is nonzero.
1890  * Set its mode if CHANGED is positive.
1891  * Set its modification time to NEWRCSTIME unless it is -1.
1892  * Return 0 on success, -1 on failure.
1893  */
1894 {
1895 	int r = 0, e = 0;
1896 #	if bad_creat0
1897 		int lr, le;
1898 #	endif
1899 
1900 	if (changed && !nerror) {
1901 		if (finptr) {
1902 			fastcopy(finptr, frewrite);
1903 			Izclose(&finptr);
1904 		}
1905 		if (1 < RCSstat.st_nlink)
1906 			rcswarn("breaking hard link");
1907 		aflush(frewrite);
1908 		seteid();
1909 		ignoreints();
1910 		r = chnamemod(
1911 			&frewrite, newRCSname, RCSname, changed,
1912 			RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
1913 			newRCStime
1914 		);
1915 		e = errno;
1916 		keepdirtemp(newRCSname);
1917 #		if bad_creat0
1918 			lr = un_link(lockname);
1919 			le = errno;
1920 			keepdirtemp(lockname);
1921 #		endif
1922 		restoreints();
1923 		setrid();
1924 		if (r != 0) {
1925 			enerror(e, RCSname);
1926 			error("saved in %s", newRCSname);
1927 		}
1928 #		if bad_creat0
1929 			if (lr != 0) {
1930 				enerror(le, lockname);
1931 				r = -1;
1932 			}
1933 #		endif
1934 	}
1935 	return r;
1936 }
1937 
1938 	void
ORCSclose()1939 ORCSclose()
1940 {
1941 	if (0 <= fdlock) {
1942 		if (close(fdlock) != 0)
1943 			efaterror(lockname);
1944 		fdlock = -1;
1945 	}
1946 	Ozclose(&frewrite);
1947 }
1948 
1949 	void
ORCSerror()1950 ORCSerror()
1951 /*
1952 * Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
1953 * Do not report errors, since this may loop.  This is needed only because
1954 * some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
1955 * some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
1956 * This isn't a completely reliable away to work around brain-damaged hosts,
1957 * because of the gap between actual file opening and setting frewrite etc.,
1958 * but it's better than nothing.
1959 */
1960 {
1961 	if (0 <= fdlock)
1962 		VOID close(fdlock);
1963 	if (frewrite)
1964 		/* Avoid fclose, since stdio may not be reentrant.  */
1965 		VOID close(fileno(frewrite));
1966 }
1967