1 /* $MirOS: src/gnu/usr.bin/rcs/src/rcsedit.c,v 1.2 2005/03/13 15:36:38 tg 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 __RCSID("$MirOS: src/gnu/usr.bin/rcs/src/rcsedit.c,v 1.2 2005/03/13 15:36:38 tg Exp $");
208 
209 __dead static void editEndsPrematurely(void);
210 __dead static void editLineNumberOverflow(void);
211 static void escape_string(FILE*,char const*);
212 static void keyreplace(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 static long editline; /* edit line counter; #lines before cursor   */
218 static long linecorr; /* #adds - #deletes in each edit run.		    */
219                /*used to correct editline in case file is not rewound after */
220                /* applying one delta                                        */
221 
222 /* indexes into dirtpname */
223 #define lockdirtp_index 0
224 #define newRCSdirtp_index bad_creat0
225 #define newworkdirtp_index (newRCSdirtp_index+1)
226 #define DIRTEMPNAMES (newworkdirtp_index + 1)
227 
228 enum maker {notmade, real, effective};
229 static struct buf dirtpname[DIRTEMPNAMES];	/* unlink these when done */
230 static enum maker volatile dirtpmaker[DIRTEMPNAMES];	/* if these are set */
231 #define lockname (dirtpname[lockdirtp_index].string)
232 #define newRCSname (dirtpname[newRCSdirtp_index].string)
233 
234 
235 	int
un_link(s)236 un_link(s)
237 	char const *s;
238 /*
239  * Remove S, even if it is unwritable.
240  * Ignore unlink() ENOENT failures; NFS generates bogus ones.
241  */
242 {
243 #	if bad_unlink
244 		if (unlink(s) == 0)
245 			return 0;
246 		else {
247 			int e = errno;
248 			/*
249 			* Forge ahead even if errno == ENOENT; some completely
250 			* brain-damaged hosts (e.g. PCTCP 2.2) yield ENOENT
251 			* even for existing unwritable files.
252 			*/
253 			if (chmod(s, S_IWUSR) != 0) {
254 				errno = e;
255 				return -1;
256 			}
257 		}
258 #	endif
259 		return unlink(s)==0 || errno==ENOENT  ?  0  :  -1;
260 }
261 
262 	static void
editEndsPrematurely()263 editEndsPrematurely()
264 {
265 	fatserror("edit script ends prematurely");
266 }
267 
268 	static void
editLineNumberOverflow()269 editLineNumberOverflow()
270 {
271 	fatserror("edit script refers to line past end of file");
272 }
273 
274 #define movelines(s1, s2, n) memmove(s1, s2, (n)*sizeof(Iptr_type))
275 
276 static void deletelines(long,long);
277 static void finisheditline(RILE*,FILE*,Iptr_type,struct hshentry const*);
278 static void insertline(unsigned long,Iptr_type);
279 static void snapshotline(FILE*,Iptr_type);
280 
281 /*
282  * `line' contains pointers to the lines in the currently `edited' file.
283  * It is a 0-origin array that represents linelim-gapsize lines.
284  * line[0 .. gap-1] and line[gap+gapsize .. linelim-1] hold pointers to lines.
285  * line[gap .. gap+gapsize-1] contains garbage.
286  *
287  * Any @s in lines are duplicated.
288  * Lines are terminated by \n, or (for a last partial line only) by single @.
289  */
290 static Iptr_type *line;
291 static size_t gap, gapsize, linelim;
292 
293 	static void
insertline(unsigned long n,Iptr_type l)294 insertline(unsigned long n, Iptr_type l)
295 /* Before line N, insert line L.  N is 0-origin.  */
296 {
297 	if (linelim-gapsize < n)
298 	    editLineNumberOverflow();
299 	if (!gapsize)
300 	    line =
301 		!linelim ?
302 			tnalloc(Iptr_type, linelim = gapsize = 1024)
303 		: (
304 			gap = gapsize = linelim,
305 			trealloc(Iptr_type, line, linelim <<= 1)
306 		);
307 	if (n < gap)
308 	    movelines(line+n+gapsize, line+n, gap-n);
309 	else if (gap < n)
310 	    movelines(line+gap, line+gap+gapsize, n-gap);
311 
312 	line[n] = l;
313 	gap = n + 1;
314 	gapsize--;
315 }
316 
317 	static void
deletelines(long n,long nlines)318 deletelines(long n, long nlines)
319 /* Delete lines N through N+NLINES-1.  N is 0-origin.  */
320 {
321 	long l = n + nlines;
322 	if (linelim-gapsize < (unsigned long)l  ||  l < n)
323 	    editLineNumberOverflow();
324 	if ((unsigned long)l < gap)
325 	    movelines(line+l+gapsize, line+l, gap-l);
326 	else if (gap < (unsigned long)n)
327 	    movelines(line+gap, line+gap+gapsize, n-gap);
328 
329 	gap = n;
330 	gapsize += nlines;
331 }
332 
333 	static void
snapshotline(f,l)334 snapshotline(f, l)
335 	register FILE *f;
336 	register Iptr_type l;
337 {
338 	register int c;
339 	do {
340 		if ((c = *l++) == SDELIM  &&  *l++ != SDELIM)
341 			return;
342 		aputc_(c, f)
343 	} while (c != '\n');
344 }
345 
346 	void
snapshotedit(f)347 snapshotedit(f)
348 	FILE *f;
349 /* Copy the current state of the edits to F.  */
350 {
351 	register Iptr_type *p, *lim, *l=line;
352 	for (p=l, lim=l+gap;  p<lim;  )
353 		snapshotline(f, *p++);
354 	for (p+=gapsize, lim=l+linelim;  p<lim;  )
355 		snapshotline(f, *p++);
356 }
357 
358 	static void
finisheditline(fin,fout,l,delta)359 finisheditline(fin, fout, l, delta)
360 	RILE *fin;
361 	FILE *fout;
362 	Iptr_type l;
363 	struct hshentry const *delta;
364 {
365 	fin->ptr = l;
366 	if (expandline(fin, fout, delta, true, (FILE*)0, true)  <  0)
367 		faterror("finisheditline internal error");
368 }
369 
370 	void
finishedit(delta,outfile,done)371 finishedit(delta, outfile, done)
372 	struct hshentry const *delta;
373 	FILE *outfile;
374 	int done;
375 /*
376  * Doing expansion if DELTA is set, output the state of the edits to OUTFILE.
377  * But do nothing unless DONE is set (which means we are on the last pass).
378  */
379 {
380 	if (done) {
381 		openfcopy(outfile);
382 		outfile = fcopy;
383 		if (!delta)
384 			snapshotedit(outfile);
385 		else {
386 			register Iptr_type *p, *lim, *l = line;
387 			register RILE *fin = finptr;
388 			Iptr_type here = fin->ptr;
389 			for (p=l, lim=l+gap;  p<lim;  )
390 				finisheditline(fin, outfile, *p++, delta);
391 			for (p+=gapsize, lim=l+linelim;  p<lim;  )
392 				finisheditline(fin, outfile, *p++, delta);
393 			fin->ptr = here;
394 		}
395 	}
396 }
397 
398 /* Open a temporary NAME for output, truncating any previous contents.  */
399 #define fopen_update_truncate(name) fopenSafer(name, FOPEN_W_WORK)
400 
401 
402 	void
openfcopy(f)403 openfcopy(f)
404 	FILE *f;
405 {
406 	if (!(fcopy = f)) {
407 		if (!resultname)
408 			resultname = maketemp(2);
409 		if (!(fcopy = fopen_update_truncate(resultname)))
410 			efaterror(resultname);
411 	}
412 }
413 
414 
415 #define copylines(upto,delta) (editline = (upto))
416 
417 	void
xpandstring(delta)418 xpandstring(delta)
419 	struct hshentry const *delta;
420 /* Function: Reads a string terminated by SDELIM from finptr and writes it
421  * to fcopy. Double SDELIM is replaced with single SDELIM.
422  * Keyword expansion is performed with data from delta.
423  * If foutptr is nonnull, the string is also copied unchanged to foutptr.
424  */
425 {
426 	while (1 < expandline(finptr,fcopy,delta,true,foutptr,true))
427 		continue;
428 }
429 
430 
431 	void
copystring()432 copystring()
433 /* Function: copies a string terminated with a single SDELIM from finptr to
434  * fcopy, replacing all double SDELIM with a single SDELIM.
435  * If foutptr is nonnull, the string also copied unchanged to foutptr.
436  * editline is incremented by the number of lines copied.
437  * Assumption: next character read is first string character.
438  */
439 {	register int c;
440 	declarecache;
441 	register FILE *frew, *fcop;
442 	register int amidline;
443 	register RILE *fin;
444 
445 	fin = finptr;
446 	setupcache(fin); cache(fin);
447 	frew = foutptr;
448 	fcop = fcopy;
449 	amidline = false;
450 	for (;;) {
451 		GETC_(frew,c)
452 		switch (c) {
453 		    case '\n':
454 			++editline;
455 			++rcsline;
456 			amidline = false;
457 			break;
458 		    case SDELIM:
459 			GETC_(frew,c)
460 			if (c != SDELIM) {
461 				/* end of string */
462 				nextc = c;
463 				editline += amidline;
464 				uncache(fin);
465 				return;
466 			}
467 			/* fall into */
468 		    default:
469 			amidline = true;
470 			break;
471                 }
472 		aputc_(c,fcop)
473         }
474 }
475 
476 
477 	void
enterstring()478 enterstring()
479 /* Like copystring, except the string is put into the edit data structure.  */
480 {
481 	register int c;
482 	declarecache;
483 	register FILE *frew;
484 	register long e, oe;
485 	register int amidline, oamidline;
486 	register Iptr_type optr;
487 	register RILE *fin;
488 
489 	e = 0;
490 	gap = 0;
491 	gapsize = linelim;
492 	fin = finptr;
493 	setupcache(fin); cache(fin);
494 	advise_access(fin, MADV_NORMAL);
495 	frew = foutptr;
496 	amidline = false;
497 	for (;;) {
498 		optr = cacheptr();
499 		GETC_(frew,c)
500 		oamidline = amidline;
501 		oe = e;
502 		switch (c) {
503 		    case '\n':
504 			++e;
505 			++rcsline;
506 			amidline = false;
507 			break;
508 		    case SDELIM:
509 			GETC_(frew,c)
510 			if (c != SDELIM) {
511 				/* end of string */
512 				nextc = c;
513 				editline = e + amidline;
514 				linecorr = 0;
515 				uncache(fin);
516 				return;
517 			}
518 			/* fall into */
519 		    default:
520 			amidline = true;
521 			break;
522 		}
523 		if (!oamidline)
524 			insertline(oe, optr);
525 	}
526 }
527 
528 	void
edit_string()529 edit_string()
530 /*
531  * Read an edit script from finptr and applies it to the edit file.
532  * If foutptr is set, the edit script is also copied verbatim to foutptr.
533  * Assumes that all these files are open.
534  * resultname is the name of the file that goes with fcopy.
535  * Assumes the next input character from finptr is the first character of
536  * the edit script. Resets nextc on exit.
537  */
538 {
539         int ed; /* editor command */
540         register int c;
541 	declarecache;
542 	register FILE *frew;
543 	register long i;
544 	register RILE *fin;
545 		register long j;
546 	struct diffcmd dc;
547 
548         editline += linecorr; linecorr=0; /*correct line number*/
549 	frew = foutptr;
550 	fin = finptr;
551 	setupcache(fin);
552 	initdiffcmd(&dc);
553 	while (0  <=  (ed = getdiffcmd(fin,true,frew,&dc)))
554 		if (!ed) {
555 			copylines(dc.line1-1, delta);
556                         /* skip over unwanted lines */
557 			i = dc.nlines;
558 			linecorr -= i;
559 			editline += i;
560 			    deletelines(editline+linecorr, i);
561 		} else {
562 			/* Copy lines without deleting any.  */
563 			copylines(dc.line1, delta);
564 			i = dc.nlines;
565 				j = editline+linecorr;
566 			linecorr += i;
567 			{
568 			    cache(fin);
569 			    do {
570 				    insertline(j++, cacheptr());
571 				for (;;) {
572 				    GETC_(frew, c)
573 				    if (c==SDELIM) {
574 					GETC_(frew, c)
575 					if (c!=SDELIM) {
576 					    if (--i)
577 						editEndsPrematurely();
578 					    nextc = c;
579 					    uncache(fin);
580 					    return;
581 					}
582 				    }
583 				    if (c == '\n')
584 					break;
585 				}
586 				++rcsline;
587 			    } while (--i);
588 			    uncache(fin);
589 			}
590                 }
591 }
592 
593 
594 
595 /* The rest is for keyword expansion */
596 
597 
598 
599 	int
expandline(infile,outfile,delta,delimstuffed,frewfile,dolog)600 expandline(infile, outfile, delta, delimstuffed, frewfile, dolog)
601 	RILE *infile;
602 	FILE *outfile, *frewfile;
603 	struct hshentry const *delta;
604 	int delimstuffed, dolog;
605 /*
606  * Read a line from INFILE and write it to OUTFILE.
607  * Do keyword expansion with data from DELTA.
608  * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
609  * If FREWFILE is set, copy the line unchanged to FREWFILE.
610  * DELIMSTUFFED must be true if FREWFILE is set.
611  * Append revision history to log only if DOLOG is set.
612  * Yields -1 if no data is copied, 0 if an incomplete line is copied,
613  * 2 if a complete line is copied; adds 1 to yield if expansion occurred.
614  */
615 {
616 	register int c;
617 	declarecache;
618 	register FILE *out, *frew;
619 	register char * tp;
620 	register int e, ds, r;
621 	char const *tlim;
622 	static struct buf keyval;
623         enum markers matchresult;
624 
625 	setupcache(infile); cache(infile);
626 	out = outfile;
627 	frew = frewfile;
628 	ds = delimstuffed;
629 	bufalloc(&keyval, keylength+3);
630 	e = 0;
631 	r = -1;
632 
633         for (;;) {
634 	    if (ds)
635 		GETC_(frew, c)
636 	    else
637 		cachegeteof_(c, goto uncache_exit;)
638 	    for (;;) {
639 		switch (c) {
640 		    case SDELIM:
641 			if (ds) {
642 			    GETC_(frew, c)
643 			    if (c != SDELIM) {
644                                 /* end of string */
645                                 nextc=c;
646 				goto uncache_exit;
647 			    }
648 			}
649 			/* fall into */
650 		    default:
651 			aputc_(c,out)
652 			r = 0;
653 			break;
654 
655 		    case '\n':
656 			rcsline += ds;
657 			aputc_(c,out)
658 			r = 2;
659 			goto uncache_exit;
660 
661 		    case KDELIM:
662 			r = 0;
663                         /* check for keyword */
664                         /* first, copy a long enough string into keystring */
665 			tp = keyval.string;
666 			*tp++ = KDELIM;
667 			for (;;) {
668 			    if (ds)
669 				GETC_(frew, c)
670 			    else
671 				cachegeteof_(c, goto keystring_eof;)
672 			    if (tp <= &keyval.string[keylength])
673 				switch (ctab[c]) {
674 				    case LETTER: case Letter:
675 					*tp++ = c;
676 					continue;
677 				    default:
678 					break;
679 				}
680 			    break;
681                         }
682 			*tp++ = c; *tp = '\0';
683 			matchresult = trymatch(keyval.string+1);
684 			if (matchresult==Nomatch) {
685 				tp[-1] = 0;
686 				aputs(keyval.string, out);
687 				continue;   /* last c handled properly */
688 			}
689 
690 			/* Now we have a keyword terminated with a K/VDELIM */
691 			if (c==VDELIM) {
692 			      /* try to find closing KDELIM, and replace value */
693 			      tlim = keyval.string + keyval.size;
694 			      for (;;) {
695 				      if (ds)
696 					GETC_(frew, c)
697 				      else
698 					cachegeteof_(c, goto keystring_eof;)
699 				      if (c=='\n' || c==KDELIM)
700 					break;
701 				      *tp++ =c;
702 				      if (tlim <= tp)
703 					  tp = bufenlarge(&keyval, &tlim);
704 				      if (c==SDELIM && ds) { /*skip next SDELIM */
705 						GETC_(frew, c)
706 						if (c != SDELIM) {
707 							/* end of string before closing KDELIM or newline */
708 							nextc = c;
709 							goto keystring_eof;
710 						}
711 				      }
712 			      }
713 			      if (c!=KDELIM) {
714 				    /* couldn't find closing KDELIM -- give up */
715 				    *tp = 0;
716 				    aputs(keyval.string, out);
717 				    continue;   /* last c handled properly */
718 			      }
719 			}
720 			/* now put out the new keyword value */
721 			uncache(infile);
722 			keyreplace(matchresult, delta, ds, infile, out, dolog);
723 			cache(infile);
724 			e = 1;
725 			break;
726                 }
727 		break;
728 	    }
729         }
730 
731     keystring_eof:
732 	*tp = 0;
733 	aputs(keyval.string, out);
734     uncache_exit:
735 	uncache(infile);
736 	return r + e;
737 }
738 
739 
740 	static void
escape_string(out,s)741 escape_string(out, s)
742 	register FILE *out;
743 	register char const *s;
744 /* Output to OUT the string S, escaping chars that would break `ci -k'.  */
745 {
746     register char c;
747     for (;;)
748 	switch ((c = *s++)) {
749 	    case 0: return;
750 	    case '\t': aputs("\\t", out); break;
751 	    case '\n': aputs("\\n", out); break;
752 	    case ' ': aputs("\\040", out); break;
753 	    case KDELIM: aputs("\\044", out); break;
754 	    case '\\': if (VERSION(5)<=RCSversion) {aputs("\\\\", out); break;}
755 	    /* fall into */
756 	    default: aputc_(c, out) break;
757 	}
758 }
759 
760 char const ciklog[ciklogsize] = "checked in with -k by ";
761 
762 	static void
keyreplace(marker,delta,delimstuffed,infile,out,dolog)763 keyreplace(marker, delta, delimstuffed, infile, out, dolog)
764 	enum markers marker;
765 	register struct hshentry const *delta;
766 	int delimstuffed;
767 	RILE *infile;
768 	register FILE *out;
769 	int dolog;
770 /* function: outputs the keyword value(s) corresponding to marker.
771  * Attributes are derived from delta.
772  */
773 {
774 	register char const *sp, *cp, *date;
775 	register int c;
776 	register size_t cs, cw, ls;
777 	char const *sp1;
778 	char datebuf[datesize + zonelenmax];
779 	int RCSv;
780 	int exp;
781 
782 	sp = Keyword[(int)marker];
783 	exp = Expand;
784 	date = delta->date;
785 	RCSv = RCSversion;
786 
787 	if (exp != VAL_EXPAND)
788 	    aprintf(out, "%c%s", KDELIM, sp);
789 	if (exp != KEY_EXPAND) {
790 
791 	    if (exp != VAL_EXPAND)
792 		aprintf(out, "%c%c", VDELIM,
793 			marker==Log && RCSv<VERSION(5)  ?  '\t'  :  ' '
794 		);
795 
796 	    switch (marker) {
797 	    case Author:
798 		aputs(delta->author, out);
799                 break;
800 	    case Date:
801 		aputs(date2str(date,datebuf), out);
802                 break;
803 	    case Id:
804 	    case LocalId:
805 	    case Header:
806 		escape_string(out,
807 			marker != Header || RCSv<VERSION(4)
808 			? basefilename(RCSname)
809 			: getfullRCSname()
810 		);
811 		aprintf(out, " %s %s %s %s",
812 			delta->num,
813 			date2str(date, datebuf),
814 			delta->author,
815 			  RCSv==VERSION(3) && delta->lockedby ? "Locked"
816 			: delta->state
817 		);
818 		if (delta->lockedby) {
819 		    if (VERSION(5) <= RCSv) {
820 			if (locker_expansion || exp==KEYVALLOCK_EXPAND)
821 			    aprintf(out, " %s", delta->lockedby);
822 		    } else if (RCSv == VERSION(4))
823 			aprintf(out, " Locker: %s", delta->lockedby);
824 		}
825                 break;
826 	    case Locker:
827 		if (delta->lockedby)
828 		    if (
829 				locker_expansion
830 			||	exp == KEYVALLOCK_EXPAND
831 			||	RCSv <= VERSION(4)
832 		    )
833 			aputs(delta->lockedby, out);
834                 break;
835 	    case Log:
836 	    case RCSfile:
837 		escape_string(out, basefilename(RCSname));
838                 break;
839 	    case Name:
840 		if (delta->name)
841 			aputs(delta->name, out);
842 		break;
843 	    case Revision:
844 		aputs(delta->num, out);
845                 break;
846 	    case Source:
847 		escape_string(out, getfullRCSname());
848                 break;
849 	    case State:
850 		aputs(delta->state, out);
851                 break;
852 	    default:
853 		break;
854 	    }
855 	    if (exp != VAL_EXPAND)
856 		afputc(' ', out);
857 	}
858 	if (exp != VAL_EXPAND)
859 	    afputc(KDELIM, out);
860 
861 	if (marker == Log   &&  dolog) {
862 		struct buf leader;
863 
864 		sp = delta->log.string;
865 		ls = delta->log.size;
866 		if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
867 			return;
868 		bufautobegin(&leader);
869 		if (RCSversion < VERSION(5)) {
870 		    cp = Comment.string;
871 		    cs = Comment.size;
872 		} else {
873 		    int kdelim_found = 0;
874 		    Ioffset_type chars_read = Itell(infile);
875 		    declarecache;
876 		    setupcache(infile); cache(infile);
877 
878 		    c = 0; /* Pacify `gcc -Wall'.  */
879 
880 		    /*
881 		    * Back up to the start of the current input line,
882 		    * setting CS to the number of characters before `$Log'.
883 		    */
884 		    cs = 0;
885 		    for (;;) {
886 			if (!--chars_read)
887 			    goto done_backing_up;
888 			cacheunget_(infile, c)
889 			if (c == '\n')
890 			    break;
891 			if (c == SDELIM  &&  delimstuffed) {
892 			    if (!--chars_read)
893 				break;
894 			    cacheunget_(infile, c)
895 			    if (c != SDELIM) {
896 				cacheget_(c)
897 				break;
898 			    }
899 			}
900 			cs += kdelim_found;
901 			kdelim_found |= c==KDELIM;
902 		    }
903 		    cacheget_(c)
904 		  done_backing_up:;
905 
906 		    /* Copy characters before `$Log' into LEADER.  */
907 		    bufalloc(&leader, cs);
908 		    cp = leader.string;
909 		    for (cw = 0;  cw < cs;  cw++) {
910 			leader.string[cw] = c;
911 			if (c == SDELIM  &&  delimstuffed) {
912 			    cacheget_(c)
913 			}
914 			cacheget_(c)
915 		    }
916 
917 		    /* Convert traditional C or Pascal leader to ` *'.  */
918 		    for (cw = 0;  cw < cs;  cw++)
919 			if (ctab[(unsigned char) cp[cw]] != SPACE)
920 			    break;
921 		    if (
922 			cw+1 < cs
923 			&&  cp[cw+1] == '*'
924 			&&  (cp[cw] == '/'  ||  cp[cw] == '(')
925 		    ) {
926 			size_t i = cw+1;
927 			for (;;)
928 			    if (++i == cs) {
929 				warn(
930 				    "`%c* $Log' is obsolescent; use ` * $Log'.",
931 				    cp[cw]
932 				);
933 				leader.string[cw] = ' ';
934 				break;
935 			    } else if (ctab[(unsigned char) cp[i]] != SPACE)
936 				break;
937 		    }
938 
939 		    /* Skip `$Log ... $' string.  */
940 		    do {
941 			cacheget_(c)
942 		    } while (c != KDELIM);
943 		    uncache(infile);
944 		}
945 		afputc('\n', out);
946 		awrite(cp, cs, out);
947 		sp1 = date2str(date, datebuf);
948 		if (VERSION(5) <= RCSv) {
949 		    aprintf(out, "Revision %s  %s  %s",
950 			delta->num, sp1, delta->author
951 		    );
952 		} else {
953 		    /* oddity: 2 spaces between date and time, not 1 as usual */
954 		    sp1 = strchr(sp1, ' ');
955 		    aprintf(out, "Revision %s  %.*s %s  %s",
956 			delta->num, (int)(sp1-datebuf), datebuf, sp1,
957 			delta->author
958 		    );
959 		}
960 		/* Do not include state: it may change and is not updated.  */
961 		cw = cs;
962 		if (VERSION(5) <= RCSv)
963 		    for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
964 			continue;
965 		for (;;) {
966 		    afputc('\n', out);
967 		    awrite(cp, cw, out);
968 		    if (!ls)
969 			break;
970 		    --ls;
971 		    c = *sp++;
972 		    if (c != '\n') {
973 			awrite(cp+cw, cs-cw, out);
974 			do {
975 			    afputc(c,out);
976 			    if (!ls)
977 				break;
978 			    --ls;
979 			    c = *sp++;
980 			} while (c != '\n');
981 		    }
982 		}
983 		bufautoend(&leader);
984 	}
985 }
986 
987 static int resolve_symlink(struct buf*);
988 
989 	static int
resolve_symlink(struct buf * L)990 resolve_symlink(struct buf *L)
991 /*
992  * If L is a symbolic link, resolve it to the name that it points to.
993  * If unsuccessful, set errno and yield -1.
994  * If it points to an existing file, yield 1.
995  * Otherwise, set errno=ENOENT and yield 0.
996  */
997 {
998 	char *b, a[SIZEABLE_PATH];
999 	int e;
1000 	size_t s;
1001 	ssize_t r;
1002 	struct buf bigbuf;
1003 	int linkcount = MAXSYMLINKS;
1004 
1005 	b = a;
1006 	s = sizeof(a);
1007 	bufautobegin(&bigbuf);
1008 	while ((r = readlink(L->string,b,s))  !=  -1)
1009 	    if ((size_t)r == s) {
1010 		bufalloc(&bigbuf, s<<1);
1011 		b = bigbuf.string;
1012 		s = bigbuf.size;
1013 	    } else if (!linkcount--) {
1014 #		ifndef ELOOP
1015 		    /*
1016 		    * Some pedantic Posix 1003.1-1990 hosts have readlink
1017 		    * but not ELOOP.  Approximate ELOOP with EMLINK.
1018 		    */
1019 #		    define ELOOP EMLINK
1020 #		endif
1021 		errno = ELOOP;
1022 		return -1;
1023 	    } else {
1024 		/* Splice symbolic link into L.  */
1025 		b[r] = '\0';
1026 		L->string[
1027 		  ROOTPATH(b)  ?  0  :  basefilename(L->string) - L->string
1028 		] = '\0';
1029 		bufscat(L, b);
1030 	    }
1031 	e = errno;
1032 	bufautoend(&bigbuf);
1033 	errno = e;
1034 	switch (e) {
1035 	    case EINVAL: return 1;
1036 	    case ENOENT: return 0;
1037 	    default: return -1;
1038 	}
1039 }
1040 
1041 	RILE *
rcswriteopen(RCSbuf,status,mustread)1042 rcswriteopen(RCSbuf, status, mustread)
1043 	struct buf *RCSbuf;
1044 	struct stat *status;
1045 	int mustread;
1046 /*
1047  * Create the lock file corresponding to RCSBUF.
1048  * Then try to open RCSBUF for reading and yield its RILE* descriptor.
1049  * Put its status into *STATUS too.
1050  * MUSTREAD is true if the file must already exist, too.
1051  * If all goes well, discard any previously acquired locks,
1052  * and set fdlock to the file descriptor of the RCS lockfile.
1053  */
1054 {
1055 	register char *tp;
1056 	register char const *sp, *RCSpath, *x;
1057 	RILE *f;
1058 	size_t l;
1059 	int e, exists, fdesc, fdescSafer, r, waslocked;
1060 	struct buf *dirt;
1061 	struct stat statbuf;
1062 
1063 	waslocked  =  0 <= fdlock;
1064 	exists = resolve_symlink(RCSbuf);
1065 	if (exists < (mustread|waslocked))
1066 		/*
1067 		 * There's an unusual problem with the RCS file;
1068 		 * or the RCS file doesn't exist,
1069 		 * and we must read or we already have a lock elsewhere.
1070 		 */
1071 		return 0;
1072 
1073 	RCSpath = RCSbuf->string;
1074 	sp = basefilename(RCSpath);
1075 	l = sp - RCSpath;
1076 	dirt = &dirtpname[waslocked];
1077 	bufscpy(dirt, RCSpath);
1078 	tp = dirt->string + l;
1079 	x = rcssuffix(RCSpath);
1080 	    if (!x) {
1081 		error("symbolic link to non RCS file `%s'", RCSpath);
1082 		errno = EINVAL;
1083 		return 0;
1084 	    }
1085 	if (*sp == *x) {
1086 		error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
1087 		errno = EINVAL;
1088 		return 0;
1089 	}
1090 	/* Create a lock filename that is a function of the RCS filename.  */
1091 	if (*x) {
1092 		/*
1093 		 * The suffix is nonempty.
1094 		 * The lock filename is the first char of of the suffix,
1095 		 * followed by the RCS filename with last char removed.  E.g.:
1096 		 *	foo,v	RCS filename with suffix ,v
1097 		 *	,foo,	lock filename
1098 		 */
1099 		*tp++ = *x;
1100 		while (*sp)
1101 			*tp++ = *sp++;
1102 		*--tp = 0;
1103 	} else {
1104 		/*
1105 		 * The suffix is empty.
1106 		 * The lock filename is the RCS filename
1107 		 * with last char replaced by '_'.
1108 		 */
1109 		while ((*tp++ = *sp++))
1110 			continue;
1111 		tp -= 2;
1112 		if (*tp == '_') {
1113 			error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
1114 			errno = EINVAL;
1115 			return 0;
1116 		}
1117 		*tp = '_';
1118 	}
1119 
1120 	sp = dirt->string;
1121 
1122 	f = 0;
1123 
1124 	/*
1125 	* good news:
1126 	*	open(f, O_CREAT|O_EXCL|O_TRUNC|..., S_IRUSR|S_IRGRP|S_IROTH)
1127 	*	is atomic according to Posix 1003.1-1990.
1128 	* bad news:
1129 	*	NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
1130 	* good news:
1131 	*	(O_TRUNC,S_IRUSR|S_IRGRP|S_IROTH) normally guarantees atomicity
1132 	*	even with NFS.
1133 	* bad news:
1134 	*	If you're root, (O_TRUNC,S_IRUSR|S_IRGRP|S_IROTH) doesn't
1135 	*	guarantee atomicity.
1136 	* good news:
1137 	*	Root-over-the-wire NFS access is rare for security reasons.
1138 	*	This bug has never been reported in practice with RCS.
1139 	* So we don't worry about this bug.
1140 	*
1141 	* An even rarer NFS bug can occur when clients retry requests.
1142 	* This can happen in the usual case of NFS over UDP.
1143 	* Suppose client A releases a lock by renaming ",f," to "f,v" at
1144 	* about the same time that client B obtains a lock by creating ",f,",
1145 	* and suppose A's first rename request is delayed, so A reissues it.
1146 	* The sequence of events might be:
1147 	*	A sends rename(",f,", "f,v")
1148 	*	B sends create(",f,")
1149 	*	A sends retry of rename(",f,", "f,v")
1150 	*	server receives, does, and acknowledges A's first rename()
1151 	*	A receives acknowledgment, and its RCS program exits
1152 	*	server receives, does, and acknowledges B's create()
1153 	*	server receives, does, and acknowledges A's retry of rename()
1154 	* This not only wrongly deletes B's lock, it removes the RCS file!
1155 	* Most NFS implementations have idempotency caches that usually prevent
1156 	* this scenario, but such caches are finite and can be overrun.
1157 	* This problem afflicts not only RCS, which uses open() and rename()
1158 	* to get and release locks; it also afflicts the traditional
1159 	* Unix method of using link() and unlink() to get and release locks,
1160 	* and the less traditional method of using mkdir() and rmdir().
1161 	* There is no easy workaround.
1162 	* Any new method based on lockf() seemingly would be incompatible with
1163 	* the old methods; besides, lockf() is notoriously buggy under NFS.
1164 	* Since this problem afflicts scads of Unix programs, but is so rare
1165 	* that nobody seems to be worried about it, we won't worry either.
1166 	*/
1167 #define create(f) open(f, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, S_IRUSR|S_IRGRP|S_IROTH)
1168 
1169 	catchints();
1170 	ignoreints();
1171 
1172 	/*
1173 	 * Create a lock file for an RCS file.  This should be atomic, i.e.
1174 	 * if two processes try it simultaneously, at most one should succeed.
1175 	 */
1176 	seteid();
1177 	fdesc = create(sp);
1178 	fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr.  */
1179 	e = errno;
1180 	setrid();
1181 
1182 	if (0 <= fdesc)
1183 		dirtpmaker[0] = effective;
1184 
1185 	if (fdescSafer < 0) {
1186 		if (e == EACCES  &&  stat(sp,&statbuf) == 0)
1187 			/* The RCS file is busy.  */
1188 			e = EEXIST;
1189 	} else {
1190 		e = ENOENT;
1191 		if (exists) {
1192 		    f = Iopen(RCSpath, FOPEN_RB, status);
1193 		    e = errno;
1194 		    if (f && waslocked) {
1195 			/* Discard the previous lock in favor of this one.  */
1196 			ORCSclose();
1197 			seteid();
1198 			r = un_link(lockname);
1199 			e = errno;
1200 			setrid();
1201 			if (r != 0)
1202 			    enfaterror(e, lockname);
1203 			bufscpy(&dirtpname[lockdirtp_index], sp);
1204 		    }
1205 		}
1206 		fdlock = fdescSafer;
1207 	}
1208 
1209 	restoreints();
1210 
1211 	errno = e;
1212 	return f;
1213 }
1214 
1215 	void
keepdirtemp(name)1216 keepdirtemp(name)
1217 	char const *name;
1218 /* Do not unlink name, either because it's not there any more,
1219  * or because it has already been unlinked.
1220  */
1221 {
1222 	register int i;
1223 	for (i=DIRTEMPNAMES; 0<=--i; )
1224 		if (dirtpname[i].string == name) {
1225 			dirtpmaker[i] = notmade;
1226 			return;
1227 		}
1228 	faterror("keepdirtemp");
1229 }
1230 
1231 	char const *
makedirtemp(isworkfile)1232 makedirtemp(isworkfile)
1233 	int isworkfile;
1234 /*
1235  * Create a unique pathname and store it into dirtpname.
1236  * Because of storage in tpnames, dirtempunlink() can unlink the file later.
1237  * Return a pointer to the pathname created.
1238  * If ISWORKFILE is 1, put it into the working file's directory;
1239  * if 0, put the unique file in RCSfile's directory.
1240  */
1241 {
1242 	register char *tp, *np;
1243 	register size_t dl;
1244 	register struct buf *bn;
1245 	register char const *name = isworkfile ? workname : RCSname;
1246 	int fd;
1247 
1248 	dl = basefilename(name) - name;
1249 	bn = &dirtpname[newRCSdirtp_index + isworkfile];
1250 	bufalloc(bn,
1251 			dl + 9
1252 	);
1253 	bufscpy(bn, name);
1254 	np = tp = bn->string;
1255 	tp += dl;
1256 	*tp++ = '_';
1257 	*tp++ = '0'+isworkfile;
1258 	catchints();
1259 		strlcpy(tp, "XXXXXX", 7);
1260 		fd = mkstemp(np);
1261 		if (fd < 0 || !*np)
1262 		    faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
1263 			(int)dl, name, '0'+isworkfile
1264 		    );
1265 		close(fd);
1266 	dirtpmaker[newRCSdirtp_index + isworkfile] = real;
1267 	return np;
1268 }
1269 
1270 	void
dirtempunlink()1271 dirtempunlink()
1272 /* Clean up makedirtemp() files.  May be invoked by signal handler. */
1273 {
1274 	register int i;
1275 	enum maker m;
1276 
1277 	for (i = DIRTEMPNAMES;  0 <= --i;  )
1278 	    if ((m = dirtpmaker[i]) != notmade) {
1279 		if (m == effective)
1280 		    seteid();
1281 		un_link(dirtpname[i].string);
1282 		if (m == effective)
1283 		    setrid();
1284 		dirtpmaker[i] = notmade;
1285 	    }
1286 }
1287 
1288 
1289 	int
chnamemod(FILE ** fromp,char const * from,char const * to,int set_mode,mode_t mode,time_t mtime)1290 chnamemod(
1291 	FILE **fromp, char const *from, char const *to,
1292 	int set_mode, mode_t mode, time_t mtime
1293 )
1294 /*
1295  * Rename a file (with stream pointer *FROMP) from FROM to TO.
1296  * FROM already exists.
1297  * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
1298  * If MTIME is not -1, change its mtime to MTIME before renaming.
1299  * Close and clear *FROMP before renaming it.
1300  * Unlink TO if it already exists.
1301  * Return -1 on error (setting errno), 0 otherwise.
1302  */
1303 {
1304 	mode_t mode_while_renaming = mode;
1305 	int fchmod_set_mode = 0;
1306 
1307 #	if bad_a_rename || bad_NFS_rename
1308 	    struct stat st;
1309 	    if (bad_NFS_rename  ||  (bad_a_rename && set_mode <= 0)) {
1310 		if (fstat(fileno(*fromp), &st) != 0)
1311 		    return -1;
1312 		if (bad_a_rename && set_mode <= 0)
1313 		    mode = st.st_mode;
1314 	    }
1315 #	endif
1316 
1317 #	if bad_a_rename
1318 		/*
1319 		* There's a short window of inconsistency
1320 		* during which the lock file is writable.
1321 		*/
1322 		mode_while_renaming = mode|S_IWUSR;
1323 		if (mode != mode_while_renaming)
1324 		    set_mode = 1;
1325 #	endif
1326 
1327 #	if has_fchmod
1328 	    if (0<set_mode  &&  fchmod(fileno(*fromp),mode_while_renaming) == 0)
1329 		fchmod_set_mode = set_mode;
1330 #	endif
1331 	/* If bad_chmod_close, we must close before chmod.  */
1332 	Ozclose(fromp);
1333 	if (fchmod_set_mode<set_mode  &&  chmod(from, mode_while_renaming) != 0)
1334 	    return -1;
1335 
1336 	if (setmtime(from, mtime) != 0)
1337 		return -1;
1338 
1339 #	if !has_rename || bad_b_rename
1340 		/*
1341 		* There's a short window of inconsistency
1342 		* during which TO does not exist.
1343 		*/
1344 		if (un_link(to) != 0  &&  errno != ENOENT)
1345 			return -1;
1346 #	endif
1347 
1348 #	if has_rename
1349 	    if (rename(from,to) != 0  &&  !(has_NFS && errno==ENOENT))
1350 		return -1;
1351 #	else
1352 	    if (do_link(from,to) != 0  ||  un_link(from) != 0)
1353 		return -1;
1354 #	endif
1355 
1356 #	if bad_NFS_rename
1357 	{
1358 	    /*
1359 	    * Check whether the rename falsely reported success.
1360 	    * A race condition can occur between the rename and the stat.
1361 	    */
1362 	    struct stat tostat;
1363 	    if (stat(to, &tostat) != 0)
1364 		return -1;
1365 	    if (! same_file(st, tostat, 0)) {
1366 		errno = EIO;
1367 		return -1;
1368 	    }
1369 	}
1370 #	endif
1371 
1372 #	if bad_a_rename
1373 	    if (0 < set_mode  &&  chmod(to, mode) != 0)
1374 		return -1;
1375 #	endif
1376 
1377 	return 0;
1378 }
1379 
1380 	int
setmtime(file,mtime)1381 setmtime(file, mtime)
1382 	char const *file;
1383 	time_t mtime;
1384 /* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1.  */
1385 {
1386 	static struct utimbuf amtime; /* static so unused fields are zero */
1387 	if (mtime == -1)
1388 		return 0;
1389 	amtime.actime = now();
1390 	amtime.modtime = mtime;
1391 	return utime(file, &amtime);
1392 }
1393 
1394 
1395 
1396 	int
findlock(delete,target)1397 findlock(delete, target)
1398 	int delete;
1399 	struct hshentry **target;
1400 /*
1401  * Find the first lock held by caller and return a pointer
1402  * to the locked delta; also removes the lock if DELETE.
1403  * If one lock, put it into *TARGET.
1404  * Return 0 for no locks, 1 for one, 2 for two or more.
1405  */
1406 {
1407 	register struct rcslock *next, **trail, **found;
1408 
1409 	found = 0;
1410 	for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
1411 		if (strcmp(getcaller(), next->login)  ==  0) {
1412 			if (found) {
1413 				rcserror("multiple revisions locked by %s; please specify one", getcaller());
1414 				return 2;
1415 			}
1416 			found = trail;
1417 		}
1418 	if (!found)
1419 		return 0;
1420 	next = *found;
1421 	*target = next->delta;
1422 	if (delete) {
1423 		next->delta->lockedby = 0;
1424 		*found = next->nextlock;
1425 	}
1426 	return 1;
1427 }
1428 
1429 	int
addlock(delta,verbose)1430 addlock(delta, verbose)
1431 	struct hshentry * delta;
1432 	int verbose;
1433 /*
1434  * Add a lock held by caller to DELTA and yield 1 if successful.
1435  * Print an error message if verbose and yield -1 if no lock is added because
1436  * DELTA is locked by somebody other than caller.
1437  * Return 0 if the caller already holds the lock.
1438  */
1439 {
1440 	register struct rcslock *next;
1441 
1442 	for (next = Locks;  next;  next = next->nextlock)
1443 		if (cmpnum(delta->num, next->delta->num) == 0) {
1444 			if (strcmp(getcaller(), next->login) == 0)
1445 				return 0;
1446 			else {
1447 				if (verbose)
1448 				  rcserror("Revision %s is already locked by %s.",
1449 					delta->num, next->login
1450 				  );
1451 				return -1;
1452 			}
1453 		}
1454 	next = ftalloc(struct rcslock);
1455 	delta->lockedby = next->login = getcaller();
1456 	next->delta = delta;
1457 	next->nextlock = Locks;
1458 	Locks = next;
1459 	return 1;
1460 }
1461 
1462 
1463 	int
addsymbol(num,name,rebind)1464 addsymbol(num, name, rebind)
1465 	char const *num, *name;
1466 	int rebind;
1467 /*
1468  * Associate with revision NUM the new symbolic NAME.
1469  * If NAME already exists and REBIND is set, associate NAME with NUM;
1470  * otherwise, print an error message and return false;
1471  * Return -1 if unsuccessful, 0 if no change, 1 if change.
1472  */
1473 {
1474 	register struct assoc *next;
1475 
1476 	for (next = Symbols;  next;  next = next->nextassoc)
1477 		if (strcmp(name, next->symbol)  ==  0) {
1478 			if (strcmp(next->num,num) == 0)
1479 				return 0;
1480 			else if (rebind) {
1481 				next->num = num;
1482 				return 1;
1483 			} else {
1484 				rcserror("symbolic name %s already bound to %s",
1485 					name, next->num
1486 				);
1487 				return -1;
1488 			}
1489 		}
1490 	next = ftalloc(struct assoc);
1491 	next->symbol = name;
1492 	next->num = num;
1493 	next->nextassoc = Symbols;
1494 	Symbols = next;
1495 	return 1;
1496 }
1497 
1498 
1499 
1500 	char const *
getcaller()1501 getcaller()
1502 /* Get the caller's login name.  */
1503 {
1504 		return getusername(euid()!=ruid());
1505 }
1506 
1507 
1508 	int
checkaccesslist()1509 checkaccesslist()
1510 /*
1511  * Return true if caller is the superuser, the owner of the
1512  * file, the access list is empty, or caller is on the access list.
1513  * Otherwise, print an error message and return false.
1514  */
1515 {
1516 	register struct access const *next;
1517 
1518 	if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
1519 		return true;
1520 
1521 	next = AccessList;
1522 	do {
1523 		if (strcmp(getcaller(), next->login)  ==  0)
1524 			return true;
1525 	} while ((next = next->nextaccess));
1526 
1527 	rcserror("user %s not on the access list", getcaller());
1528 	return false;
1529 }
1530 
1531 
1532 	int
dorewrite(lockflag,changed)1533 dorewrite(lockflag, changed)
1534 	int lockflag, changed;
1535 /*
1536  * Do nothing if LOCKFLAG is zero.
1537  * Prepare to rewrite an RCS file if CHANGED is positive.
1538  * Stop rewriting if CHANGED is zero, because there won't be any changes.
1539  * Fail if CHANGED is negative.
1540  * Return 0 on success, -1 on failure.
1541  */
1542 {
1543 	int r = 0, e;
1544 
1545 	if (lockflag) {
1546 		if (changed) {
1547 			if (changed < 0)
1548 				return -1;
1549 			putadmin();
1550 			puttree(Head, frewrite);
1551 			aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
1552 			foutptr = frewrite;
1553 		} else {
1554 #			if bad_creat0
1555 				int nr = !!frewrite, ne = 0;
1556 #			endif
1557 			ORCSclose();
1558 			seteid();
1559 			ignoreints();
1560 #			if bad_creat0
1561 				if (nr) {
1562 					nr = un_link(newRCSname);
1563 					ne = errno;
1564 					keepdirtemp(newRCSname);
1565 				}
1566 #			endif
1567 			r = un_link(lockname);
1568 			e = errno;
1569 			keepdirtemp(lockname);
1570 			restoreints();
1571 			setrid();
1572 			if (r != 0)
1573 				enerror(e, lockname);
1574 #			if bad_creat0
1575 				if (nr != 0) {
1576 					enerror(ne, newRCSname);
1577 					r = -1;
1578 				}
1579 #			endif
1580 		}
1581 	    }
1582 	return r;
1583 }
1584 
1585 	int
donerewrite(changed,newRCStime)1586 donerewrite(changed, newRCStime)
1587 	int changed;
1588 	time_t newRCStime;
1589 /*
1590  * Finish rewriting an RCS file if CHANGED is nonzero.
1591  * Set its mode if CHANGED is positive.
1592  * Set its modification time to NEWRCSTIME unless it is -1.
1593  * Return 0 on success, -1 on failure.
1594  */
1595 {
1596 	int r = 0, e = 0;
1597 #	if bad_creat0
1598 		int lr, le;
1599 #	endif
1600 
1601 	if (changed && !nerror) {
1602 		if (finptr) {
1603 			fastcopy(finptr, frewrite);
1604 			Izclose(&finptr);
1605 		}
1606 		if (1 < RCSstat.st_nlink)
1607 			rcswarn("breaking hard link");
1608 		aflush(frewrite);
1609 		seteid();
1610 		ignoreints();
1611 		r = chnamemod(
1612 			&frewrite, newRCSname, RCSname, changed,
1613 			RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
1614 			newRCStime
1615 		);
1616 		e = errno;
1617 		keepdirtemp(newRCSname);
1618 #		if bad_creat0
1619 			lr = un_link(lockname);
1620 			le = errno;
1621 			keepdirtemp(lockname);
1622 #		endif
1623 		restoreints();
1624 		setrid();
1625 		if (r != 0) {
1626 			enerror(e, RCSname);
1627 			error("saved in %s", newRCSname);
1628 		}
1629 #		if bad_creat0
1630 			if (lr != 0) {
1631 				enerror(le, lockname);
1632 				r = -1;
1633 			}
1634 #		endif
1635 	}
1636 	return r;
1637 }
1638 
1639 	void
ORCSclose()1640 ORCSclose()
1641 {
1642 	if (0 <= fdlock) {
1643 		if (close(fdlock) != 0)
1644 			efaterror(lockname);
1645 		fdlock = -1;
1646 	}
1647 	Ozclose(&frewrite);
1648 }
1649 
1650 	void
ORCSerror()1651 ORCSerror()
1652 /*
1653 * Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
1654 * Do not report errors, since this may loop.  This is needed only because
1655 * some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
1656 * some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
1657 * This isn't a completely reliable away to work around brain-damaged hosts,
1658 * because of the gap between actual file opening and setting frewrite etc.,
1659 * but it's better than nothing.
1660 */
1661 {
1662 	if (0 <= fdlock)
1663 		close(fdlock);
1664 	if (frewrite)
1665 		/* Avoid fclose, since stdio may not be reentrant.  */
1666 		close(fileno(frewrite));
1667 }
1668