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