xref: /netbsd/external/gpl2/rcs/dist/src/rcsedit.c (revision 0d69c6cf)
1 /*	$NetBSD: rcsedit.c,v 1.2 2016/01/14 04:22:39 christos Exp $	*/
2 
3 /* RCS stream editor */
4 
5 /******************************************************************************
6  *                       edits the input file according to a
7  *                       script from stdin, generated by diff -n
8  *                       performs keyword expansion
9  ******************************************************************************
10  */
11 
12 /* Copyright 1982, 1988, 1989 Walter Tichy
13    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
14    Distributed under license by the Free Software Foundation, Inc.
15 
16 This file is part of RCS.
17 
18 RCS is free software; you can redistribute it and/or modify
19 it under the terms of the GNU General Public License as published by
20 the Free Software Foundation; either version 2, or (at your option)
21 any later version.
22 
23 RCS is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26 GNU General Public License for more details.
27 
28 You should have received a copy of the GNU General Public License
29 along with RCS; see the file COPYING.
30 If not, write to the Free Software Foundation,
31 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
32 
33 Report problems and direct all questions to:
34 
35     rcs-bugs@cs.purdue.edu
36 
37 */
38 
39 /*
40  * Log: rcsedit.c,v
41  * Revision 5.19  1995/06/16 06:19:24  eggert
42  * Update FSF address.
43  *
44  * Revision 5.18  1995/06/01 16:23:43  eggert
45  * (dirtpname): No longer external.
46  * (do_link): Simplify logic.
47  * (finisheditline, finishedit): Replace Iseek/Itell with what they stand for.
48  * (fopen_update_truncate): Replace `#if' with `if'.
49  * (keyreplace, makedirtemp): dirlen(x) -> basefilename(x)-x.
50  *
51  * (edit_string): Fix bug: if !large_memory, a bogus trailing `@' was output
52  * at the end of incomplete lines.
53  *
54  * (keyreplace): Do not assume that seeking backwards
55  * at the start of a file will fail; on some systems it succeeds.
56  * Convert C- and Pascal-style comment starts to ` *' in comment leader.
57  *
58  * (rcswriteopen): Use fdSafer to get safer file descriptor.
59  * Open RCS file with FOPEN_RB.
60  *
61  * (chnamemod): Work around bad_NFS_rename bug; don't ignore un_link result.
62  * Fall back on chmod if fchmod fails, since it might be ENOSYS.
63  *
64  * (aflush): Move to rcslex.c.
65  *
66  * Revision 5.17  1994/03/20 04:52:58  eggert
67  * Normally calculate the $Log prefix from context, not from RCS file.
68  * Move setmtime here from rcsutil.c.  Add ORCSerror.  Remove lint.
69  *
70  * Revision 5.16  1993/11/03 17:42:27  eggert
71  * Add -z.  Add Name keyword.  If bad_unlink, ignore errno when unlink fails.
72  * Escape white space, $, and \ in keyword string file names.
73  * Don't output 2 spaces between date and time after Log.
74  *
75  * Revision 5.15  1992/07/28  16:12:44  eggert
76  * Some hosts have readlink but not ELOOP.  Avoid `unsigned'.
77  * Preserve dates more systematically.  Statement macro names now end in _.
78  *
79  * Revision 5.14  1992/02/17  23:02:24  eggert
80  * Add -T support.
81  *
82  * Revision 5.13  1992/01/24  18:44:19  eggert
83  * Add support for bad_chmod_close, bad_creat0.
84  *
85  * Revision 5.12  1992/01/06  02:42:34  eggert
86  * Add setmode parameter to chnamemod.  addsymbol now reports changes.
87  * while (E) ; -> while (E) continue;
88  *
89  * Revision 5.11  1991/11/03  01:11:44  eggert
90  * Move the warning about link breaking to where they're actually being broken.
91  *
92  * Revision 5.10  1991/10/07  17:32:46  eggert
93  * Support piece tables even if !has_mmap.  Fix rare NFS bugs.
94  *
95  * Revision 5.9  1991/09/17  19:07:40  eggert
96  * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
97  *
98  * Revision 5.8  1991/08/19  03:13:55  eggert
99  * Add piece tables, NFS bug workarounds.  Catch odd filenames.  Tune.
100  *
101  * Revision 5.7  1991/04/21  11:58:21  eggert
102  * Fix errno bugs.  Add -x, RCSINIT, MS-DOS support.
103  *
104  * Revision 5.6  1991/02/25  07:12:40  eggert
105  * Fix setuid bug.  Support new link behavior.  Work around broken "w+" fopen.
106  *
107  * Revision 5.5  1990/12/30  05:07:35  eggert
108  * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL).
109  *
110  * Revision 5.4  1990/11/01  05:03:40  eggert
111  * Permit arbitrary data in comment leaders.
112  *
113  * Revision 5.3  1990/09/11  02:41:13  eggert
114  * Tune expandline().
115  *
116  * Revision 5.2  1990/09/04  08:02:21  eggert
117  * Count RCS lines better.  Improve incomplete line handling.
118  *
119  * Revision 5.1  1990/08/29  07:13:56  eggert
120  * Add -kkvl.
121  * Fix bug when getting revisions to files ending in incomplete lines.
122  * Fix bug in comment leader expansion.
123  *
124  * Revision 5.0  1990/08/22  08:12:47  eggert
125  * Don't require final newline.
126  * Don't append "checked in with -k by " to logs,
127  * so that checking in a program with -k doesn't change it.
128  * Don't generate trailing white space for empty comment leader.
129  * Remove compile-time limits; use malloc instead.  Add -k, -V.
130  * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
131  * Ansify and Posixate.  Check diff's output.
132  *
133  * Revision 4.8  89/05/01  15:12:35  narten
134  * changed copyright header to reflect current distribution rules
135  *
136  * Revision 4.7  88/11/08  13:54:14  narten
137  * misplaced semicolon caused infinite loop
138  *
139  * Revision 4.6  88/08/09  19:12:45  eggert
140  * Shrink stdio code size; allow cc -R.
141  *
142  * Revision 4.5  87/12/18  11:38:46  narten
143  * Changes from the 43. version. Don't know the significance of the
144  * first change involving "rewind". Also, additional "lint" cleanup.
145  * (Guy Harris)
146  *
147  * Revision 4.4  87/10/18  10:32:21  narten
148  * Updating version numbers. Changes relative to version 1.1 actually
149  * relative to 4.1
150  *
151  * Revision 1.4  87/09/24  13:59:29  narten
152  * Sources now pass through lint (if you ignore printf/sprintf/fprintf
153  * warnings)
154  *
155  * Revision 1.3  87/09/15  16:39:39  shepler
156  * added an initializatin of the variables editline and linecorr
157  * this will be done each time a file is processed.
158  * (there was an obscure bug where if co was used to retrieve multiple files
159  *  it would dump)
160  * fix attributed to  Roy Morris @FileNet Corp ...!felix!roy
161  *
162  * Revision 1.2  87/03/27  14:22:17  jenkins
163  * Port to suns
164  *
165  * Revision 4.1  83/05/12  13:10:30  wft
166  * Added new markers Id and RCSfile; added locker to Header and Id.
167  * Overhauled expandline completely() (problem with $01234567890123456789@).
168  * Moved trymatch() and marker table to rcskeys.c.
169  *
170  * Revision 3.7  83/05/12  13:04:39  wft
171  * Added retry to expandline to resume after failed match which ended in $.
172  * Fixed truncation problem for $19chars followed by@@.
173  * Log no longer expands full path of RCS file.
174  *
175  * Revision 3.6  83/05/11  16:06:30  wft
176  * added retry to expandline to resume after failed match which ended in $.
177  * Fixed truncation problem for $19chars followed by@@.
178  *
179  * Revision 3.5  82/12/04  13:20:56  wft
180  * Added expansion of keyword Locker.
181  *
182  * Revision 3.4  82/12/03  12:26:54  wft
183  * Added line number correction in case editing does not start at the
184  * beginning of the file.
185  * Changed keyword expansion to always print a space before closing KDELIM;
186  * Expansion for Header shortened.
187  *
188  * Revision 3.3  82/11/14  14:49:30  wft
189  * removed Suffix from keyword expansion. Replaced fclose with ffclose.
190  * keyreplace() gets log message from delta, not from curlogmsg.
191  * fixed expression overflow in while(c=putc(GETC....
192  * checked nil printing.
193  *
194  * Revision 3.2  82/10/18  21:13:39  wft
195  * I added checks for write errors during the co process, and renamed
196  * expandstring() to xpandstring().
197  *
198  * Revision 3.1  82/10/13  15:52:55  wft
199  * changed type of result of getc() from char to int.
200  * made keyword expansion loop in expandline() portable to machines
201  * without sign-extension.
202  */
203 
204 
205 #include "rcsbase.h"
206 
207 libId(editId, "Id: rcsedit.c,v 5.19 1995/06/16 06:19:24 eggert 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 {	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 	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 Header:
1056 #ifdef LOCALID
1057 	    case LocalId:
1058 #endif
1059 		escape_string(out,
1060 			marker!=Header || RCSv<VERSION(4)
1061 			? basefilename(RCSname)
1062 			: getfullRCSname()
1063 		);
1064 		aprintf(out, " %s %s %s %s",
1065 			delta->num,
1066 			date2str(date, datebuf),
1067 			delta->author,
1068 			  RCSv==VERSION(3) && delta->lockedby ? "Locked"
1069 			: delta->state
1070 		);
1071 		if (delta->lockedby) {
1072 		    if (VERSION(5) <= RCSv) {
1073 			if (locker_expansion || exp==KEYVALLOCK_EXPAND)
1074 			    aprintf(out, " %s", delta->lockedby);
1075 		    } else if (RCSv == VERSION(4))
1076 			aprintf(out, " Locker: %s", delta->lockedby);
1077 		}
1078                 break;
1079 	    case Locker:
1080 		if (delta->lockedby)
1081 		    if (
1082 				locker_expansion
1083 			||	exp == KEYVALLOCK_EXPAND
1084 			||	RCSv <= VERSION(4)
1085 		    )
1086 			aputs(delta->lockedby, out);
1087                 break;
1088 	    case Log:
1089 	    case RCSfile:
1090 		escape_string(out, basefilename(RCSname));
1091                 break;
1092 	    case Name:
1093 		if (delta->name)
1094 			aputs(delta->name, out);
1095 		break;
1096 	    case Revision:
1097 		aputs(delta->num, out);
1098                 break;
1099 	    case Source:
1100 		escape_string(out, getfullRCSname());
1101                 break;
1102 	    case State:
1103 		aputs(delta->state, out);
1104                 break;
1105 	    default:
1106 		break;
1107 	    }
1108 	    if (exp != VAL_EXPAND)
1109 		afputc(' ', out);
1110 	}
1111 	if (exp != VAL_EXPAND)
1112 	    afputc(KDELIM, out);
1113 
1114 	if (marker == Log   &&  dolog) {
1115 		struct buf leader;
1116 
1117 		sp = delta->log.string;
1118 		ls = delta->log.size;
1119 		if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
1120 			return;
1121 		bufautobegin(&leader);
1122 		if (RCSversion < VERSION(5)) {
1123 		    cp = Comment.string;
1124 		    cs = Comment.size;
1125 		} else {
1126 		    int kdelim_found = 0;
1127 		    Ioffset_type chars_read = Itell(infile);
1128 		    declarecache;
1129 		    setupcache(infile); cache(infile);
1130 
1131 		    c = 0; /* Pacify `gcc -Wall'.  */
1132 
1133 		    /*
1134 		    * Back up to the start of the current input line,
1135 		    * setting CS to the number of characters before `$Log'.
1136 		    */
1137 		    cs = 0;
1138 		    for (;;) {
1139 			if (!--chars_read)
1140 			    goto done_backing_up;
1141 			cacheunget_(infile, c)
1142 			if (c == '\n')
1143 			    break;
1144 			if (c == SDELIM  &&  delimstuffed) {
1145 			    if (!--chars_read)
1146 				break;
1147 			    cacheunget_(infile, c)
1148 			    if (c != SDELIM) {
1149 				cacheget_(c)
1150 				break;
1151 			    }
1152 			}
1153 			cs += kdelim_found;
1154 			kdelim_found |= c==KDELIM;
1155 		    }
1156 		    cacheget_(c)
1157 		  done_backing_up:;
1158 
1159 		    /* Copy characters before `$Log' into LEADER.  */
1160 		    bufalloc(&leader, cs);
1161 		    cp = leader.string;
1162 		    for (cw = 0;  cw < cs;  cw++) {
1163 			leader.string[cw] = c;
1164 			if (c == SDELIM  &&  delimstuffed)
1165 			    cacheget_(c)
1166 			cacheget_(c)
1167 		    }
1168 
1169 		    /* Convert traditional C or Pascal leader to ` *'.  */
1170 		    for (cw = 0;  cw < cs;  cw++)
1171 			if (ctab[(unsigned char) cp[cw]] != SPACE)
1172 			    break;
1173 		    if (
1174 			cw+1 < cs
1175 			&&  cp[cw+1] == '*'
1176 			&&  (cp[cw] == '/'  ||  cp[cw] == '(')
1177 		    ) {
1178 			size_t i = cw+1;
1179 			for (;;)
1180 			    if (++i == cs) {
1181 				warn(
1182 				    "`%c* Log' is obsolescent; use ` * Log'.",
1183 				    cp[cw]
1184 				);
1185 				leader.string[cw] = ' ';
1186 				break;
1187 			    } else if (ctab[(unsigned char) cp[i]] != SPACE)
1188 				break;
1189 		    }
1190 
1191 		    /* Skip `Log ... ' string.  */
1192 		    do {
1193 			cacheget_(c)
1194 		    } while (c != KDELIM);
1195 		    uncache(infile);
1196 		}
1197 		afputc('\n', out);
1198 		awrite(cp, cs, out);
1199 		sp1 = date2str(date, datebuf);
1200 		if (VERSION(5) <= RCSv) {
1201 		    aprintf(out, "Revision %s  %s  %s",
1202 			delta->num, sp1, delta->author
1203 		    );
1204 		} else {
1205 		    /* oddity: 2 spaces between date and time, not 1 as usual */
1206 		    sp1 = strchr(sp1, ' ');
1207 		    aprintf(out, "Revision %s  %.*s %s  %s",
1208 			delta->num, (int)(sp1-datebuf), datebuf, sp1,
1209 			delta->author
1210 		    );
1211 		}
1212 		/* Do not include state: it may change and is not updated.  */
1213 		cw = cs;
1214 		if (VERSION(5) <= RCSv)
1215 		    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
1216 			continue;
1217 		for (;;) {
1218 		    afputc('\n', out);
1219 		    awrite(cp, cw, out);
1220 		    if (!ls)
1221 			break;
1222 		    --ls;
1223 		    c = *sp++;
1224 		    if (c != '\n') {
1225 			awrite(cp+cw, cs-cw, out);
1226 			do {
1227 			    afputc(c,out);
1228 			    if (!ls)
1229 				break;
1230 			    --ls;
1231 			    c = *sp++;
1232 			} while (c != '\n');
1233 		    }
1234 		}
1235 		bufautoend(&leader);
1236 	}
1237 }
1238 
1239 #if has_readlink
1240 	static int resolve_symlink P((struct buf*));
1241 	static int
resolve_symlink(L)1242 resolve_symlink(L)
1243 	struct buf *L;
1244 /*
1245  * If L is a symbolic link, resolve it to the name that it points to.
1246  * If unsuccessful, set errno and yield -1.
1247  * If it points to an existing file, yield 1.
1248  * Otherwise, set errno=ENOENT and yield 0.
1249  */
1250 {
1251 	char *b, a[SIZEABLE_PATH];
1252 	int e;
1253 	size_t s;
1254 	ssize_t r;
1255 	struct buf bigbuf;
1256 	int linkcount = MAXSYMLINKS;
1257 
1258 	b = a;
1259 	s = sizeof(a);
1260 	bufautobegin(&bigbuf);
1261 	while ((r = readlink(L->string,b,s))  !=  -1)
1262 	    if (r == s) {
1263 		bufalloc(&bigbuf, s<<1);
1264 		b = bigbuf.string;
1265 		s = bigbuf.size;
1266 	    } else if (!linkcount--) {
1267 #		ifndef ELOOP
1268 		    /*
1269 		    * Some pedantic Posix 1003.1-1990 hosts have readlink
1270 		    * but not ELOOP.  Approximate ELOOP with EMLINK.
1271 		    */
1272 #		    define ELOOP EMLINK
1273 #		endif
1274 		errno = ELOOP;
1275 		return -1;
1276 	    } else {
1277 		/* Splice symbolic link into L.  */
1278 		b[r] = '\0';
1279 		L->string[
1280 		  ROOTPATH(b)  ?  0  :  basefilename(L->string) - L->string
1281 		] = '\0';
1282 		bufscat(L, b);
1283 	    }
1284 	e = errno;
1285 	bufautoend(&bigbuf);
1286 	errno = e;
1287 	switch (e) {
1288 	    case readlink_isreg_errno: return 1;
1289 	    case ENOENT: return 0;
1290 	    default: return -1;
1291 	}
1292 }
1293 #endif
1294 
1295 	RILE *
rcswriteopen(RCSbuf,status,mustread)1296 rcswriteopen(RCSbuf, status, mustread)
1297 	struct buf *RCSbuf;
1298 	struct stat *status;
1299 	int mustread;
1300 /*
1301  * Create the lock file corresponding to RCSBUF.
1302  * Then try to open RCSBUF for reading and yield its RILE* descriptor.
1303  * Put its status into *STATUS too.
1304  * MUSTREAD is true if the file must already exist, too.
1305  * If all goes well, discard any previously acquired locks,
1306  * and set fdlock to the file descriptor of the RCS lockfile.
1307  */
1308 {
1309 	register char *tp;
1310 	register char const *sp, *RCSpath, *x;
1311 	RILE *f;
1312 	size_t l;
1313 	int e, exists, fdesc, fdescSafer, r, waslocked;
1314 	struct buf *dirt;
1315 	struct stat statbuf;
1316 
1317 	waslocked  =  0 <= fdlock;
1318 	exists =
1319 #		if has_readlink
1320 			resolve_symlink(RCSbuf);
1321 #		else
1322 			    stat(RCSbuf->string, &statbuf) == 0  ?  1
1323 			:   errno==ENOENT ? 0 : -1;
1324 #		endif
1325 	if (exists < (mustread|waslocked))
1326 		/*
1327 		 * There's an unusual problem with the RCS file;
1328 		 * or the RCS file doesn't exist,
1329 		 * and we must read or we already have a lock elsewhere.
1330 		 */
1331 		return 0;
1332 
1333 	RCSpath = RCSbuf->string;
1334 	sp = basefilename(RCSpath);
1335 	l = sp - RCSpath;
1336 	dirt = &dirtpname[waslocked];
1337 	bufscpy(dirt, RCSpath);
1338 	tp = dirt->string + l;
1339 	x = rcssuffix(RCSpath);
1340 #	if has_readlink
1341 	    if (!x) {
1342 		error("symbolic link to non RCS file `%s'", RCSpath);
1343 		errno = EINVAL;
1344 		return 0;
1345 	    }
1346 #	endif
1347 	if (*sp == *x) {
1348 		error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
1349 		errno = EINVAL;
1350 		return 0;
1351 	}
1352 	/* Create a lock filename that is a function of the RCS filename.  */
1353 	if (*x) {
1354 		/*
1355 		 * The suffix is nonempty.
1356 		 * The lock filename is the first char of of the suffix,
1357 		 * followed by the RCS filename with last char removed.  E.g.:
1358 		 *	foo,v	RCS filename with suffix ,v
1359 		 *	,foo,	lock filename
1360 		 */
1361 		*tp++ = *x;
1362 		while (*sp)
1363 			*tp++ = *sp++;
1364 		*--tp = 0;
1365 	} else {
1366 		/*
1367 		 * The suffix is empty.
1368 		 * The lock filename is the RCS filename
1369 		 * with last char replaced by '_'.
1370 		 */
1371 		while ((*tp++ = *sp++))
1372 			continue;
1373 		tp -= 2;
1374 		if (*tp == '_') {
1375 			error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
1376 			errno = EINVAL;
1377 			return 0;
1378 		}
1379 		*tp = '_';
1380 	}
1381 
1382 	sp = dirt->string;
1383 
1384 	f = 0;
1385 
1386 	/*
1387 	* good news:
1388 	*	open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
1389 	*	is atomic according to Posix 1003.1-1990.
1390 	* bad news:
1391 	*	NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
1392 	* good news:
1393 	*	(O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity
1394 	*	even with NFS.
1395 	* bad news:
1396 	*	If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
1397 	*	guarantee atomicity.
1398 	* good news:
1399 	*	Root-over-the-wire NFS access is rare for security reasons.
1400 	*	This bug has never been reported in practice with RCS.
1401 	* So we don't worry about this bug.
1402 	*
1403 	* An even rarer NFS bug can occur when clients retry requests.
1404 	* This can happen in the usual case of NFS over UDP.
1405 	* Suppose client A releases a lock by renaming ",f," to "f,v" at
1406 	* about the same time that client B obtains a lock by creating ",f,",
1407 	* and suppose A's first rename request is delayed, so A reissues it.
1408 	* The sequence of events might be:
1409 	*	A sends rename(",f,", "f,v")
1410 	*	B sends create(",f,")
1411 	*	A sends retry of rename(",f,", "f,v")
1412 	*	server receives, does, and acknowledges A's first rename()
1413 	*	A receives acknowledgment, and its RCS program exits
1414 	*	server receives, does, and acknowledges B's create()
1415 	*	server receives, does, and acknowledges A's retry of rename()
1416 	* This not only wrongly deletes B's lock, it removes the RCS file!
1417 	* Most NFS implementations have idempotency caches that usually prevent
1418 	* this scenario, but such caches are finite and can be overrun.
1419 	* This problem afflicts not only RCS, which uses open() and rename()
1420 	* to get and release locks; it also afflicts the traditional
1421 	* Unix method of using link() and unlink() to get and release locks,
1422 	* and the less traditional method of using mkdir() and rmdir().
1423 	* There is no easy workaround.
1424 	* Any new method based on lockf() seemingly would be incompatible with
1425 	* the old methods; besides, lockf() is notoriously buggy under NFS.
1426 	* Since this problem afflicts scads of Unix programs, but is so rare
1427 	* that nobody seems to be worried about it, we won't worry either.
1428 	*/
1429 #	if !open_can_creat
1430 #		define create(f) creat(f, OPEN_CREAT_READONLY)
1431 #	else
1432 #		define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
1433 #	endif
1434 
1435 	catchints();
1436 	ignoreints();
1437 
1438 	/*
1439 	 * Create a lock file for an RCS file.  This should be atomic, i.e.
1440 	 * if two processes try it simultaneously, at most one should succeed.
1441 	 */
1442 	seteid();
1443 	fdesc = create(sp);
1444 	fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr.  */
1445 	e = errno;
1446 	setrid();
1447 
1448 	if (0 <= fdesc)
1449 		dirtpmaker[0] = effective;
1450 
1451 	if (fdescSafer < 0) {
1452 		if (e == EACCES  &&  stat(sp,&statbuf) == 0)
1453 			/* The RCS file is busy.  */
1454 			e = EEXIST;
1455 	} else {
1456 		e = ENOENT;
1457 		if (exists) {
1458 		    f = Iopen(RCSpath, FOPEN_RB, status);
1459 		    e = errno;
1460 		    if (f && waslocked) {
1461 			/* Discard the previous lock in favor of this one.  */
1462 			ORCSclose();
1463 			seteid();
1464 			r = un_link(lockname);
1465 			e = errno;
1466 			setrid();
1467 			if (r != 0)
1468 			    enfaterror(e, lockname);
1469 			bufscpy(&dirtpname[lockdirtp_index], sp);
1470 		    }
1471 		}
1472 		fdlock = fdescSafer;
1473 	}
1474 
1475 	restoreints();
1476 
1477 	errno = e;
1478 	return f;
1479 }
1480 
1481 	void
keepdirtemp(name)1482 keepdirtemp(name)
1483 	char const *name;
1484 /* Do not unlink name, either because it's not there any more,
1485  * or because it has already been unlinked.
1486  */
1487 {
1488 	register int i;
1489 	for (i=DIRTEMPNAMES; 0<=--i; )
1490 		if (dirtpname[i].string == name) {
1491 			dirtpmaker[i] = notmade;
1492 			return;
1493 		}
1494 	faterror("keepdirtemp");
1495 }
1496 
1497 	char const *
makedirtemp(isworkfile)1498 makedirtemp(isworkfile)
1499 	int isworkfile;
1500 /*
1501  * Create a unique pathname and store it into dirtpname.
1502  * Because of storage in tpnames, dirtempunlink() can unlink the file later.
1503  * Return a pointer to the pathname created.
1504  * If ISWORKFILE is 1, put it into the working file's directory;
1505  * if 0, put the unique file in RCSfile's directory.
1506  */
1507 {
1508 	register char *tp, *np;
1509 	register size_t dl;
1510 	register struct buf *bn;
1511 	register char const *name = isworkfile ? workname : RCSname;
1512 #	if has_mkstemp
1513 	int fd;
1514 #	endif
1515 
1516 	dl = basefilename(name) - name;
1517 	bn = &dirtpname[newRCSdirtp_index + isworkfile];
1518 	bufalloc(bn,
1519 #		if has_mktemp
1520 			dl + 9
1521 #		else
1522 			strlen(name) + 3
1523 #		endif
1524 	);
1525 	bufscpy(bn, name);
1526 	np = tp = bn->string;
1527 	tp += dl;
1528 	*tp++ = '_';
1529 	*tp++ = '0'+isworkfile;
1530 	catchints();
1531 #	if has_mktemp
1532 		VOID strcpy(tp, "XXXXXX");
1533 #		if has_mkstemp
1534 		if ((fd = mkstemp(np)) == -1)
1535 #		else
1536 		if (!mktemp(np) || !*np)
1537 #		endif
1538 		    faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
1539 			(int)dl, name, '0'+isworkfile
1540 		    );
1541 #		if has_mkstemp
1542 		close(fd);
1543 #		endif
1544 #	else
1545 		/*
1546 		 * Posix 1003.1-1990 has no reliable way
1547 		 * to create a unique file in a named directory.
1548 		 * We fudge here.  If the filename is abcde,
1549 		 * the temp filename is _Ncde where N is a digit.
1550 		 */
1551 		name += dl;
1552 		if (*name) name++;
1553 		if (*name) name++;
1554 		VOID strcpy(tp, name);
1555 #	endif
1556 	dirtpmaker[newRCSdirtp_index + isworkfile] = real;
1557 	return np;
1558 }
1559 
1560 	void
dirtempunlink()1561 dirtempunlink()
1562 /* Clean up makedirtemp() files.  May be invoked by signal handler. */
1563 {
1564 	register int i;
1565 	enum maker m;
1566 
1567 	for (i = DIRTEMPNAMES;  0 <= --i;  )
1568 	    if ((m = dirtpmaker[i]) != notmade) {
1569 		if (m == effective)
1570 		    seteid();
1571 		VOID un_link(dirtpname[i].string);
1572 		if (m == effective)
1573 		    setrid();
1574 		dirtpmaker[i] = notmade;
1575 	    }
1576 }
1577 
1578 
1579 	int
1580 #if has_prototypes
chnamemod(FILE ** fromp,char const * from,char const * to,int set_mode,mode_t mode,time_t mtime)1581 chnamemod(
1582 	FILE **fromp, char const *from, char const *to,
1583 	int set_mode, mode_t mode, time_t mtime
1584 )
1585   /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1586 #else
1587   chnamemod(fromp, from, to, set_mode, mode, mtime)
1588 	FILE **fromp; char const *from,*to;
1589 	int set_mode; mode_t mode; time_t mtime;
1590 #endif
1591 /*
1592  * Rename a file (with stream pointer *FROMP) from FROM to TO.
1593  * FROM already exists.
1594  * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
1595  * If MTIME is not -1, change its mtime to MTIME before renaming.
1596  * Close and clear *FROMP before renaming it.
1597  * Unlink TO if it already exists.
1598  * Return -1 on error (setting errno), 0 otherwise.
1599  */
1600 {
1601 	mode_t mode_while_renaming = mode;
1602 	int fchmod_set_mode = 0;
1603 
1604 #	if bad_a_rename || bad_NFS_rename
1605 	    struct stat st;
1606 	    if (bad_NFS_rename  ||  (bad_a_rename && set_mode <= 0)) {
1607 		if (fstat(fileno(*fromp), &st) != 0)
1608 		    return -1;
1609 		if (bad_a_rename && set_mode <= 0)
1610 		    mode = st.st_mode;
1611 	    }
1612 #	endif
1613 
1614 #	if bad_a_rename
1615 		/*
1616 		* There's a short window of inconsistency
1617 		* during which the lock file is writable.
1618 		*/
1619 		mode_while_renaming = mode|S_IWUSR;
1620 		if (mode != mode_while_renaming)
1621 		    set_mode = 1;
1622 #	endif
1623 
1624 #	if has_fchmod
1625 	    if (0<set_mode  &&  fchmod(fileno(*fromp),mode_while_renaming) == 0)
1626 		fchmod_set_mode = set_mode;
1627 #	endif
1628 	/* If bad_chmod_close, we must close before chmod.  */
1629 	Ozclose(fromp);
1630 	if (fchmod_set_mode<set_mode  &&  chmod(from, mode_while_renaming) != 0)
1631 	    return -1;
1632 
1633 	if (setmtime(from, mtime) != 0)
1634 		return -1;
1635 
1636 #	if !has_rename || bad_b_rename
1637 		/*
1638 		* There's a short window of inconsistency
1639 		* during which TO does not exist.
1640 		*/
1641 		if (un_link(to) != 0  &&  errno != ENOENT)
1642 			return -1;
1643 #	endif
1644 
1645 #	if has_rename
1646 	    if (rename(from,to) != 0  &&  !(has_NFS && errno==ENOENT))
1647 		return -1;
1648 #	else
1649 	    if (do_link(from,to) != 0  ||  un_link(from) != 0)
1650 		return -1;
1651 #	endif
1652 
1653 #	if bad_NFS_rename
1654 	{
1655 	    /*
1656 	    * Check whether the rename falsely reported success.
1657 	    * A race condition can occur between the rename and the stat.
1658 	    */
1659 	    struct stat tostat;
1660 	    if (stat(to, &tostat) != 0)
1661 		return -1;
1662 	    if (! same_file(st, tostat, 0)) {
1663 		errno = EIO;
1664 		return -1;
1665 	    }
1666 	}
1667 #	endif
1668 
1669 #	if bad_a_rename
1670 	    if (0 < set_mode  &&  chmod(to, mode) != 0)
1671 		return -1;
1672 #	endif
1673 
1674 	return 0;
1675 }
1676 
1677 	int
setmtime(file,mtime)1678 setmtime(file, mtime)
1679 	char const *file;
1680 	time_t mtime;
1681 /* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1.  */
1682 {
1683 	static struct utimbuf amtime; /* static so unused fields are zero */
1684 	if (mtime == -1)
1685 		return 0;
1686 	amtime.actime = now();
1687 	amtime.modtime = mtime;
1688 	return utime(file, &amtime);
1689 }
1690 
1691 
1692 
1693 	int
findlock(delete,target)1694 findlock(delete, target)
1695 	int delete;
1696 	struct hshentry **target;
1697 /*
1698  * Find the first lock held by caller and return a pointer
1699  * to the locked delta; also removes the lock if DELETE.
1700  * If one lock, put it into *TARGET.
1701  * Return 0 for no locks, 1 for one, 2 for two or more.
1702  */
1703 {
1704 	register struct rcslock *next, **trail, **found;
1705 
1706 	found = 0;
1707 	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
1708 		if (strcmp(getcaller(), next->login)  ==  0) {
1709 			if (found) {
1710 				rcserror("multiple revisions locked by %s; please specify one", getcaller());
1711 				return 2;
1712 			}
1713 			found = trail;
1714 		}
1715 	if (!found)
1716 		return 0;
1717 	next = *found;
1718 	*target = next->delta;
1719 	if (delete) {
1720 		next->delta->lockedby = 0;
1721 		*found = next->nextlock;
1722 	}
1723 	return 1;
1724 }
1725 
1726 	int
addlock(delta,verbose)1727 addlock(delta, verbose)
1728 	struct hshentry * delta;
1729 	int verbose;
1730 /*
1731  * Add a lock held by caller to DELTA and yield 1 if successful.
1732  * Print an error message if verbose and yield -1 if no lock is added because
1733  * DELTA is locked by somebody other than caller.
1734  * Return 0 if the caller already holds the lock.
1735  */
1736 {
1737 	register struct rcslock *next;
1738 
1739 	for (next = Locks;  next;  next = next->nextlock)
1740 		if (cmpnum(delta->num, next->delta->num) == 0) {
1741 			if (strcmp(getcaller(), next->login) == 0)
1742 				return 0;
1743 			else {
1744 				if (verbose)
1745 				  rcserror("Revision %s is already locked by %s.",
1746 					delta->num, next->login
1747 				  );
1748 				return -1;
1749 			}
1750 		}
1751 	next = ftalloc(struct rcslock);
1752 	delta->lockedby = next->login = getcaller();
1753 	next->delta = delta;
1754 	next->nextlock = Locks;
1755 	Locks = next;
1756 	return 1;
1757 }
1758 
1759 
1760 	int
addsymbol(num,name,rebind)1761 addsymbol(num, name, rebind)
1762 	char const *num, *name;
1763 	int rebind;
1764 /*
1765  * Associate with revision NUM the new symbolic NAME.
1766  * If NAME already exists and REBIND is set, associate NAME with NUM;
1767  * otherwise, print an error message and return false;
1768  * Return -1 if unsuccessful, 0 if no change, 1 if change.
1769  */
1770 {
1771 	register struct assoc *next;
1772 
1773 	for (next = Symbols;  next;  next = next->nextassoc)
1774 		if (strcmp(name, next->symbol)  ==  0) {
1775 			if (strcmp(next->num,num) == 0)
1776 				return 0;
1777 			else if (rebind) {
1778 				next->num = num;
1779 				return 1;
1780 			} else {
1781 				rcserror("symbolic name %s already bound to %s",
1782 					name, next->num
1783 				);
1784 				return -1;
1785 			}
1786 		}
1787 	next = ftalloc(struct assoc);
1788 	next->symbol = name;
1789 	next->num = num;
1790 	next->nextassoc = Symbols;
1791 	Symbols = next;
1792 	return 1;
1793 }
1794 
1795 
1796 
1797 	char const *
getcaller()1798 getcaller()
1799 /* Get the caller's login name.  */
1800 {
1801 #	if has_setuid
1802 		return getusername(euid()!=ruid());
1803 #	else
1804 		return getusername(false);
1805 #	endif
1806 }
1807 
1808 
1809 	int
checkaccesslist()1810 checkaccesslist()
1811 /*
1812  * Return true if caller is the superuser, the owner of the
1813  * file, the access list is empty, or caller is on the access list.
1814  * Otherwise, print an error message and return false.
1815  */
1816 {
1817 	register struct access const *next;
1818 
1819 	if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
1820 		return true;
1821 
1822 	next = AccessList;
1823 	do {
1824 		if (strcmp(getcaller(), next->login)  ==  0)
1825 			return true;
1826 	} while ((next = next->nextaccess));
1827 
1828 	rcserror("user %s not on the access list", getcaller());
1829 	return false;
1830 }
1831 
1832 
1833 	int
dorewrite(lockflag,changed)1834 dorewrite(lockflag, changed)
1835 	int lockflag, changed;
1836 /*
1837  * Do nothing if LOCKFLAG is zero.
1838  * Prepare to rewrite an RCS file if CHANGED is positive.
1839  * Stop rewriting if CHANGED is zero, because there won't be any changes.
1840  * Fail if CHANGED is negative.
1841  * Return 0 on success, -1 on failure.
1842  */
1843 {
1844 	int r = 0, e;
1845 
1846 	if (lockflag) {
1847 		if (changed) {
1848 			if (changed < 0)
1849 				return -1;
1850 			putadmin();
1851 			puttree(Head, frewrite);
1852 			aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
1853 			foutptr = frewrite;
1854 		} else {
1855 #			if bad_creat0
1856 				int nr = !!frewrite, ne = 0;
1857 #			endif
1858 			ORCSclose();
1859 			seteid();
1860 			ignoreints();
1861 #			if bad_creat0
1862 				if (nr) {
1863 					nr = un_link(newRCSname);
1864 					ne = errno;
1865 					keepdirtemp(newRCSname);
1866 				}
1867 #			endif
1868 			r = un_link(lockname);
1869 			e = errno;
1870 			keepdirtemp(lockname);
1871 			restoreints();
1872 			setrid();
1873 			if (r != 0)
1874 				enerror(e, lockname);
1875 #			if bad_creat0
1876 				if (nr != 0) {
1877 					enerror(ne, newRCSname);
1878 					r = -1;
1879 				}
1880 #			endif
1881 		}
1882 	}
1883 	return r;
1884 }
1885 
1886 	int
donerewrite(changed,newRCStime)1887 donerewrite(changed, newRCStime)
1888 	int changed;
1889 	time_t newRCStime;
1890 /*
1891  * Finish rewriting an RCS file if CHANGED is nonzero.
1892  * Set its mode if CHANGED is positive.
1893  * Set its modification time to NEWRCSTIME unless it is -1.
1894  * Return 0 on success, -1 on failure.
1895  */
1896 {
1897 	int r = 0, e = 0;
1898 #	if bad_creat0
1899 		int lr, le;
1900 #	endif
1901 
1902 	if (changed && !nerror) {
1903 		if (finptr) {
1904 			fastcopy(finptr, frewrite);
1905 			Izclose(&finptr);
1906 		}
1907 		if (1 < RCSstat.st_nlink)
1908 			rcswarn("breaking hard link");
1909 		aflush(frewrite);
1910 		seteid();
1911 		ignoreints();
1912 		r = chnamemod(
1913 			&frewrite, newRCSname, RCSname, changed,
1914 			RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
1915 			newRCStime
1916 		);
1917 		e = errno;
1918 		keepdirtemp(newRCSname);
1919 #		if bad_creat0
1920 			lr = un_link(lockname);
1921 			le = errno;
1922 			keepdirtemp(lockname);
1923 #		endif
1924 		restoreints();
1925 		setrid();
1926 		if (r != 0) {
1927 			enerror(e, RCSname);
1928 			error("saved in %s", newRCSname);
1929 		}
1930 #		if bad_creat0
1931 			if (lr != 0) {
1932 				enerror(le, lockname);
1933 				r = -1;
1934 			}
1935 #		endif
1936 	}
1937 	return r;
1938 }
1939 
1940 	void
ORCSclose()1941 ORCSclose()
1942 {
1943 	if (0 <= fdlock) {
1944 		if (close(fdlock) != 0)
1945 			efaterror(lockname);
1946 		fdlock = -1;
1947 	}
1948 	Ozclose(&frewrite);
1949 }
1950 
1951 	void
ORCSerror()1952 ORCSerror()
1953 /*
1954 * Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
1955 * Do not report errors, since this may loop.  This is needed only because
1956 * some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
1957 * some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
1958 * This isn't a completely reliable away to work around brain-damaged hosts,
1959 * because of the gap between actual file opening and setting frewrite etc.,
1960 * but it's better than nothing.
1961 */
1962 {
1963 	if (0 <= fdlock)
1964 		VOID close(fdlock);
1965 	if (frewrite)
1966 		/* Avoid fclose, since stdio may not be reentrant.  */
1967 		VOID close(fileno(frewrite));
1968 }
1969