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