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