xref: /dragonfly/gnu/usr.bin/rcs/lib/rcsedit.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
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  * $FreeBSD: src/gnu/usr.bin/rcs/lib/rcsedit.c,v 1.11.2.1 2001/05/12 10:29:42 kris Exp $
39  * $DragonFly: src/gnu/usr.bin/rcs/lib/rcsedit.c,v 1.2 2003/06/17 04:25:47 dillon Exp $
40  *
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, "$DragonFly: src/gnu/usr.bin/rcs/lib/rcsedit.c,v 1.2 2003/06/17 04:25:47 dillon 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 {         register 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           register 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 LocalId:
1056               case Header:
1057               case CVSHeader:
1058                     if (marker == Id || RCSv < VERSION(4) ||
1059                         (marker == LocalId && LocalIdMode == Id))
1060                               escape_string(out, basefilename(RCSname));
1061                     else if (marker == CVSHeader ||
1062                         (marker == LocalId && LocalIdMode == CVSHeader))
1063                               escape_string(out, getfullCVSname());
1064                     else
1065                               escape_string(out, getfullRCSname());
1066                     aprintf(out, " %s %s %s %s",
1067                               delta->num,
1068                               date2str(date, datebuf),
1069                               delta->author,
1070                                 RCSv==VERSION(3) && delta->lockedby ? "Locked"
1071                               : delta->state
1072                     );
1073                     if (delta->lockedby) {
1074                         if (VERSION(5) <= RCSv) {
1075                               if (locker_expansion || exp==KEYVALLOCK_EXPAND)
1076                                   aprintf(out, " %s", delta->lockedby);
1077                         } else if (RCSv == VERSION(4))
1078                               aprintf(out, " Locker: %s", delta->lockedby);
1079                     }
1080                 break;
1081               case Locker:
1082                     if (delta->lockedby)
1083                         if (
1084                                         locker_expansion
1085                               ||        exp == KEYVALLOCK_EXPAND
1086                               ||        RCSv <= VERSION(4)
1087                         )
1088                               aputs(delta->lockedby, out);
1089                 break;
1090               case Log:
1091               case RCSfile:
1092                     escape_string(out, basefilename(RCSname));
1093                 break;
1094               case Name:
1095                     if (delta->name)
1096                               aputs(delta->name, out);
1097                     break;
1098               case Revision:
1099                     aputs(delta->num, out);
1100                 break;
1101               case Source:
1102                     escape_string(out, getfullRCSname());
1103                 break;
1104               case State:
1105                     aputs(delta->state, out);
1106                 break;
1107               default:
1108                     break;
1109               }
1110               if (exp != VAL_EXPAND)
1111                     afputc(' ', out);
1112           }
1113           if (exp != VAL_EXPAND)
1114               afputc(KDELIM, out);
1115 
1116           if (marker == Log   &&  dolog) {
1117                     struct buf leader;
1118 
1119                     sp = delta->log.string;
1120                     ls = delta->log.size;
1121                     if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
1122                               return;
1123                     bufautobegin(&leader);
1124                     if (RCSversion < VERSION(5)) {
1125                         cp = Comment.string;
1126                         cs = Comment.size;
1127                     } else {
1128                         int kdelim_found = 0;
1129                         Ioffset_type chars_read = Itell(infile);
1130                         declarecache;
1131                         setupcache(infile); cache(infile);
1132 
1133                         c = 0; /* Pacify `gcc -Wall'.  */
1134 
1135                         /*
1136                         * Back up to the start of the current input line,
1137                         * setting CS to the number of characters before `$Log'.
1138                         */
1139                         cs = 0;
1140                         for (;;) {
1141                               if (!--chars_read)
1142                                   goto done_backing_up;
1143                               cacheunget_(infile, c)
1144                               if (c == '\n')
1145                                   break;
1146                               if (c == SDELIM  &&  delimstuffed) {
1147                                   if (!--chars_read)
1148                                         break;
1149                                   cacheunget_(infile, c)
1150                                   if (c != SDELIM) {
1151                                         cacheget_(c)
1152                                         break;
1153                                   }
1154                               }
1155                               cs += kdelim_found;
1156                               kdelim_found |= c==KDELIM;
1157                         }
1158                         cacheget_(c)
1159                       done_backing_up:;
1160 
1161                         /* Copy characters before `$Log' into LEADER.  */
1162                         bufalloc(&leader, cs);
1163                         cp = leader.string;
1164                         for (cw = 0;  cw < cs;  cw++) {
1165                               leader.string[cw] = c;
1166                               if (c == SDELIM  &&  delimstuffed) {
1167                                   cacheget_(c)
1168                               }
1169                               cacheget_(c)
1170                         }
1171 
1172                         /* Convert traditional C or Pascal leader to ` *'.  */
1173                         for (cw = 0;  cw < cs;  cw++)
1174                               if (ctab[(unsigned char) cp[cw]] != SPACE)
1175                                   break;
1176                         if (
1177                               cw+1 < cs
1178                               &&  cp[cw+1] == '*'
1179                               &&  (cp[cw] == '/'  ||  cp[cw] == '(')
1180                         ) {
1181                               size_t i = cw+1;
1182                               for (;;)
1183                                   if (++i == cs) {
1184                                         warn(
1185                                             "`%c* $Log' is obsolescent; use ` * $Log'.",
1186                                             cp[cw]
1187                                         );
1188                                         leader.string[cw] = ' ';
1189                                         break;
1190                                   } else if (ctab[(unsigned char) cp[i]] != SPACE)
1191                                         break;
1192                         }
1193 
1194                         /* Skip `$Log ... $' string.  */
1195                         do {
1196                               cacheget_(c)
1197                         } while (c != KDELIM);
1198                         uncache(infile);
1199                     }
1200                     afputc('\n', out);
1201                     awrite(cp, cs, out);
1202                     sp1 = date2str(date, datebuf);
1203                     if (VERSION(5) <= RCSv) {
1204                         aprintf(out, "Revision %s  %s  %s",
1205                               delta->num, sp1, delta->author
1206                         );
1207                     } else {
1208                         /* oddity: 2 spaces between date and time, not 1 as usual */
1209                         sp1 = strchr(sp1, ' ');
1210                         aprintf(out, "Revision %s  %.*s %s  %s",
1211                               delta->num, (int)(sp1-datebuf), datebuf, sp1,
1212                               delta->author
1213                         );
1214                     }
1215                     /* Do not include state: it may change and is not updated.  */
1216                     cw = cs;
1217                     if (VERSION(5) <= RCSv)
1218                         for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
1219                               continue;
1220                     for (;;) {
1221                         afputc('\n', out);
1222                         awrite(cp, cw, out);
1223                         if (!ls)
1224                               break;
1225                         --ls;
1226                         c = *sp++;
1227                         if (c != '\n') {
1228                               awrite(cp+cw, cs-cw, out);
1229                               do {
1230                                   afputc(c,out);
1231                                   if (!ls)
1232                                         break;
1233                                   --ls;
1234                                   c = *sp++;
1235                               } while (c != '\n');
1236                         }
1237                     }
1238                     bufautoend(&leader);
1239           }
1240 }
1241 
1242 #if has_readlink
1243           static int resolve_symlink P((struct buf*));
1244           static int
resolve_symlink(L)1245 resolve_symlink(L)
1246           struct buf *L;
1247 /*
1248  * If L is a symbolic link, resolve it to the name that it points to.
1249  * If unsuccessful, set errno and yield -1.
1250  * If it points to an existing file, yield 1.
1251  * Otherwise, set errno=ENOENT and yield 0.
1252  */
1253 {
1254           char *b, a[SIZEABLE_PATH];
1255           int e;
1256           size_t s;
1257           ssize_t r;
1258           struct buf bigbuf;
1259           int linkcount = MAXSYMLINKS;
1260 
1261           b = a;
1262           s = sizeof(a);
1263           bufautobegin(&bigbuf);
1264           while ((r = readlink(L->string,b,s))  !=  -1)
1265               if (r == s) {
1266                     bufalloc(&bigbuf, s<<1);
1267                     b = bigbuf.string;
1268                     s = bigbuf.size;
1269               } else if (!linkcount--) {
1270 #                   ifndef ELOOP
1271                         /*
1272                         * Some pedantic Posix 1003.1-1990 hosts have readlink
1273                         * but not ELOOP.  Approximate ELOOP with EMLINK.
1274                         */
1275 #                       define ELOOP EMLINK
1276 #                   endif
1277                     errno = ELOOP;
1278                     return -1;
1279               } else {
1280                     /* Splice symbolic link into L.  */
1281                     b[r] = '\0';
1282                     L->string[
1283                       ROOTPATH(b)  ?  0  :  basefilename(L->string) - L->string
1284                     ] = '\0';
1285                     bufscat(L, b);
1286               }
1287           e = errno;
1288           bufautoend(&bigbuf);
1289           errno = e;
1290           switch (e) {
1291               case readlink_isreg_errno: return 1;
1292               case ENOENT: return 0;
1293               default: return -1;
1294           }
1295 }
1296 #endif
1297 
1298           RILE *
rcswriteopen(RCSbuf,status,mustread)1299 rcswriteopen(RCSbuf, status, mustread)
1300           struct buf *RCSbuf;
1301           struct stat *status;
1302           int mustread;
1303 /*
1304  * Create the lock file corresponding to RCSBUF.
1305  * Then try to open RCSBUF for reading and yield its RILE* descriptor.
1306  * Put its status into *STATUS too.
1307  * MUSTREAD is true if the file must already exist, too.
1308  * If all goes well, discard any previously acquired locks,
1309  * and set fdlock to the file descriptor of the RCS lockfile.
1310  */
1311 {
1312           register char *tp;
1313           register char const *sp, *RCSpath, *x;
1314           RILE *f;
1315           size_t l;
1316           int e, exists, fdesc, fdescSafer, r, waslocked;
1317           struct buf *dirt;
1318           struct stat statbuf;
1319 
1320           waslocked  =  0 <= fdlock;
1321           exists =
1322 #                   if has_readlink
1323                               resolve_symlink(RCSbuf);
1324 #                   else
1325                                   stat(RCSbuf->string, &statbuf) == 0  ?  1
1326                               :   errno==ENOENT ? 0 : -1;
1327 #                   endif
1328           if (exists < (mustread|waslocked))
1329                     /*
1330                      * There's an unusual problem with the RCS file;
1331                      * or the RCS file doesn't exist,
1332                      * and we must read or we already have a lock elsewhere.
1333                      */
1334                     return 0;
1335 
1336           RCSpath = RCSbuf->string;
1337           sp = basefilename(RCSpath);
1338           l = sp - RCSpath;
1339           dirt = &dirtpname[waslocked];
1340           bufscpy(dirt, RCSpath);
1341           tp = dirt->string + l;
1342           x = rcssuffix(RCSpath);
1343 #         if has_readlink
1344               if (!x) {
1345                     error("symbolic link to non RCS file `%s'", RCSpath);
1346                     errno = EINVAL;
1347                     return 0;
1348               }
1349 #         endif
1350           if (*sp == *x) {
1351                     error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
1352                     errno = EINVAL;
1353                     return 0;
1354           }
1355           /* Create a lock filename that is a function of the RCS filename.  */
1356           if (*x) {
1357                     /*
1358                      * The suffix is nonempty.
1359                      * The lock filename is the first char of of the suffix,
1360                      * followed by the RCS filename with last char removed.  E.g.:
1361                      *        foo,v     RCS filename with suffix ,v
1362                      *        ,foo,     lock filename
1363                      */
1364                     *tp++ = *x;
1365                     while (*sp)
1366                               *tp++ = *sp++;
1367                     *--tp = 0;
1368           } else {
1369                     /*
1370                      * The suffix is empty.
1371                      * The lock filename is the RCS filename
1372                      * with last char replaced by '_'.
1373                      */
1374                     while ((*tp++ = *sp++))
1375                               continue;
1376                     tp -= 2;
1377                     if (*tp == '_') {
1378                               error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
1379                               errno = EINVAL;
1380                               return 0;
1381                     }
1382                     *tp = '_';
1383           }
1384 
1385           sp = dirt->string;
1386 
1387           f = 0;
1388 
1389           /*
1390           * good news:
1391           *         open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
1392           *         is atomic according to Posix 1003.1-1990.
1393           * bad news:
1394           *         NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
1395           * good news:
1396           *         (O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity
1397           *         even with NFS.
1398           * bad news:
1399           *         If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
1400           *         guarantee atomicity.
1401           * good news:
1402           *         Root-over-the-wire NFS access is rare for security reasons.
1403           *         This bug has never been reported in practice with RCS.
1404           * So we don't worry about this bug.
1405           *
1406           * An even rarer NFS bug can occur when clients retry requests.
1407           * This can happen in the usual case of NFS over UDP.
1408           * Suppose client A releases a lock by renaming ",f," to "f,v" at
1409           * about the same time that client B obtains a lock by creating ",f,",
1410           * and suppose A's first rename request is delayed, so A reissues it.
1411           * The sequence of events might be:
1412           *         A sends rename(",f,", "f,v")
1413           *         B sends create(",f,")
1414           *         A sends retry of rename(",f,", "f,v")
1415           *         server receives, does, and acknowledges A's first rename()
1416           *         A receives acknowledgment, and its RCS program exits
1417           *         server receives, does, and acknowledges B's create()
1418           *         server receives, does, and acknowledges A's retry of rename()
1419           * This not only wrongly deletes B's lock, it removes the RCS file!
1420           * Most NFS implementations have idempotency caches that usually prevent
1421           * this scenario, but such caches are finite and can be overrun.
1422           * This problem afflicts not only RCS, which uses open() and rename()
1423           * to get and release locks; it also afflicts the traditional
1424           * Unix method of using link() and unlink() to get and release locks,
1425           * and the less traditional method of using mkdir() and rmdir().
1426           * There is no easy workaround.
1427           * Any new method based on lockf() seemingly would be incompatible with
1428           * the old methods; besides, lockf() is notoriously buggy under NFS.
1429           * Since this problem afflicts scads of Unix programs, but is so rare
1430           * that nobody seems to be worried about it, we won't worry either.
1431           */
1432 #         if !open_can_creat
1433 #                   define create(f) creat(f, OPEN_CREAT_READONLY)
1434 #         else
1435 #                   define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
1436 #         endif
1437 
1438           catchints();
1439           ignoreints();
1440 
1441           /*
1442            * Create a lock file for an RCS file.  This should be atomic, i.e.
1443            * if two processes try it simultaneously, at most one should succeed.
1444            */
1445           seteid();
1446           fdesc = create(sp);
1447           fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr.  */
1448           e = errno;
1449           setrid();
1450 
1451           if (0 <= fdesc)
1452                     dirtpmaker[0] = effective;
1453 
1454           if (fdescSafer < 0) {
1455                     if (e == EACCES  &&  stat(sp,&statbuf) == 0)
1456                               /* The RCS file is busy.  */
1457                               e = EEXIST;
1458           } else {
1459                     e = ENOENT;
1460                     if (exists) {
1461                         f = Iopen(RCSpath, FOPEN_RB, status);
1462                         e = errno;
1463                         if (f && waslocked) {
1464                               /* Discard the previous lock in favor of this one.  */
1465                               ORCSclose();
1466                               seteid();
1467                               r = un_link(lockname);
1468                               e = errno;
1469                               setrid();
1470                               if (r != 0)
1471                                   enfaterror(e, lockname);
1472                               bufscpy(&dirtpname[lockdirtp_index], sp);
1473                         }
1474                     }
1475                     fdlock = fdescSafer;
1476           }
1477 
1478           restoreints();
1479 
1480           errno = e;
1481           return f;
1482 }
1483 
1484           void
keepdirtemp(name)1485 keepdirtemp(name)
1486           char const *name;
1487 /* Do not unlink name, either because it's not there any more,
1488  * or because it has already been unlinked.
1489  */
1490 {
1491           register int i;
1492           for (i=DIRTEMPNAMES; 0<=--i; )
1493                     if (dirtpname[i].string == name) {
1494                               dirtpmaker[i] = notmade;
1495                               return;
1496                     }
1497           faterror("keepdirtemp");
1498 }
1499 
1500           char const *
makedirtemp(isworkfile)1501 makedirtemp(isworkfile)
1502           int isworkfile;
1503 /*
1504  * Create a unique pathname and store it into dirtpname.
1505  * Because of storage in tpnames, dirtempunlink() can unlink the file later.
1506  * Return a pointer to the pathname created.
1507  * If ISWORKFILE is 1, put it into the working file's directory;
1508  * if 0, put the unique file in RCSfile's directory.
1509  */
1510 {
1511           register char *tp, *np;
1512           register size_t dl;
1513           register struct buf *bn;
1514           register char const *name = isworkfile ? workname : RCSname;
1515 #         if has_mktemp
1516           int fd;
1517 #         endif
1518 
1519           dl = basefilename(name) - name;
1520           bn = &dirtpname[newRCSdirtp_index + isworkfile];
1521           bufalloc(bn,
1522 #                   if has_mktemp
1523                               dl + 9
1524 #                   else
1525                               strlen(name) + 3
1526 #                   endif
1527           );
1528           bufscpy(bn, name);
1529           np = tp = bn->string;
1530           tp += dl;
1531           *tp++ = '_';
1532           *tp++ = '0'+isworkfile;
1533           catchints();
1534 #         if has_mktemp
1535                     VOID strcpy(tp, "XXXXXX");
1536                     fd = mkstemp(np);
1537                     if (fd < 0 || !*np)
1538                         faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
1539                               (int)dl, name, '0'+isworkfile
1540                         );
1541                     close(fd);
1542 #         else
1543                     /*
1544                      * Posix 1003.1-1990 has no reliable way
1545                      * to create a unique file in a named directory.
1546                      * We fudge here.  If the filename is abcde,
1547                      * the temp filename is _Ncde where N is a digit.
1548                      */
1549                     name += dl;
1550                     if (*name) name++;
1551                     if (*name) name++;
1552                     VOID strcpy(tp, name);
1553 #         endif
1554           dirtpmaker[newRCSdirtp_index + isworkfile] = real;
1555           return np;
1556 }
1557 
1558           void
dirtempunlink()1559 dirtempunlink()
1560 /* Clean up makedirtemp() files.  May be invoked by signal handler. */
1561 {
1562           register int i;
1563           enum maker m;
1564 
1565           for (i = DIRTEMPNAMES;  0 <= --i;  )
1566               if ((m = dirtpmaker[i]) != notmade) {
1567                     if (m == effective)
1568                         seteid();
1569                     VOID un_link(dirtpname[i].string);
1570                     if (m == effective)
1571                         setrid();
1572                     dirtpmaker[i] = notmade;
1573               }
1574 }
1575 
1576 
1577           int
1578 #if has_prototypes
chnamemod(FILE ** fromp,char const * from,char const * to,int set_mode,mode_t mode,time_t mtime)1579 chnamemod(
1580           FILE **fromp, char const *from, char const *to,
1581           int set_mode, mode_t mode, time_t mtime
1582 )
1583   /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1584 #else
1585   chnamemod(fromp, from, to, set_mode, mode, mtime)
1586           FILE **fromp; char const *from,*to;
1587           int set_mode; mode_t mode; time_t mtime;
1588 #endif
1589 /*
1590  * Rename a file (with stream pointer *FROMP) from FROM to TO.
1591  * FROM already exists.
1592  * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
1593  * If MTIME is not -1, change its mtime to MTIME before renaming.
1594  * Close and clear *FROMP before renaming it.
1595  * Unlink TO if it already exists.
1596  * Return -1 on error (setting errno), 0 otherwise.
1597  */
1598 {
1599           mode_t mode_while_renaming = mode;
1600           int fchmod_set_mode = 0;
1601 
1602 #         if bad_a_rename || bad_NFS_rename
1603               struct stat st;
1604               if (bad_NFS_rename  ||  (bad_a_rename && set_mode <= 0)) {
1605                     if (fstat(fileno(*fromp), &st) != 0)
1606                         return -1;
1607                     if (bad_a_rename && set_mode <= 0)
1608                         mode = st.st_mode;
1609               }
1610 #         endif
1611 
1612 #         if bad_a_rename
1613                     /*
1614                     * There's a short window of inconsistency
1615                     * during which the lock file is writable.
1616                     */
1617                     mode_while_renaming = mode|S_IWUSR;
1618                     if (mode != mode_while_renaming)
1619                         set_mode = 1;
1620 #         endif
1621 
1622 #         if has_fchmod
1623               if (0<set_mode  &&  fchmod(fileno(*fromp),mode_while_renaming) == 0)
1624                     fchmod_set_mode = set_mode;
1625 #         endif
1626           /* If bad_chmod_close, we must close before chmod.  */
1627           Ozclose(fromp);
1628           if (fchmod_set_mode<set_mode  &&  chmod(from, mode_while_renaming) != 0)
1629               return -1;
1630 
1631           if (setmtime(from, mtime) != 0)
1632                     return -1;
1633 
1634 #         if !has_rename || bad_b_rename
1635                     /*
1636                     * There's a short window of inconsistency
1637                     * during which TO does not exist.
1638                     */
1639                     if (un_link(to) != 0  &&  errno != ENOENT)
1640                               return -1;
1641 #         endif
1642 
1643 #         if has_rename
1644               if (rename(from,to) != 0  &&  !(has_NFS && errno==ENOENT))
1645                     return -1;
1646 #         else
1647               if (do_link(from,to) != 0  ||  un_link(from) != 0)
1648                     return -1;
1649 #         endif
1650 
1651 #         if bad_NFS_rename
1652           {
1653               /*
1654               * Check whether the rename falsely reported success.
1655               * A race condition can occur between the rename and the stat.
1656               */
1657               struct stat tostat;
1658               if (stat(to, &tostat) != 0)
1659                     return -1;
1660               if (! same_file(st, tostat, 0)) {
1661                     errno = EIO;
1662                     return -1;
1663               }
1664           }
1665 #         endif
1666 
1667 #         if bad_a_rename
1668               if (0 < set_mode  &&  chmod(to, mode) != 0)
1669                     return -1;
1670 #         endif
1671 
1672           return 0;
1673 }
1674 
1675           int
setmtime(file,mtime)1676 setmtime(file, mtime)
1677           char const *file;
1678           time_t mtime;
1679 /* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1.  */
1680 {
1681           static struct utimbuf amtime; /* static so unused fields are zero */
1682           if (mtime == -1)
1683                     return 0;
1684           amtime.actime = now();
1685           amtime.modtime = mtime;
1686           return utime(file, &amtime);
1687 }
1688 
1689 
1690 
1691           int
findlock(delete,target)1692 findlock(delete, target)
1693           int delete;
1694           struct hshentry **target;
1695 /*
1696  * Find the first lock held by caller and return a pointer
1697  * to the locked delta; also removes the lock if DELETE.
1698  * If one lock, put it into *TARGET.
1699  * Return 0 for no locks, 1 for one, 2 for two or more.
1700  */
1701 {
1702           register struct rcslock *next, **trail, **found;
1703 
1704           found = 0;
1705           for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
1706                     if (strcmp(getcaller(), next->login)  ==  0) {
1707                               if (found) {
1708                                         rcserror("multiple revisions locked by %s; please specify one", getcaller());
1709                                         return 2;
1710                               }
1711                               found = trail;
1712                     }
1713           if (!found)
1714                     return 0;
1715           next = *found;
1716           *target = next->delta;
1717           if (delete) {
1718                     next->delta->lockedby = 0;
1719                     *found = next->nextlock;
1720           }
1721           return 1;
1722 }
1723 
1724           int
addlock(delta,verbose)1725 addlock(delta, verbose)
1726           struct hshentry * delta;
1727           int verbose;
1728 /*
1729  * Add a lock held by caller to DELTA and yield 1 if successful.
1730  * Print an error message if verbose and yield -1 if no lock is added because
1731  * DELTA is locked by somebody other than caller.
1732  * Return 0 if the caller already holds the lock.
1733  */
1734 {
1735           register struct rcslock *next;
1736 
1737           for (next = Locks;  next;  next = next->nextlock)
1738                     if (cmpnum(delta->num, next->delta->num) == 0) {
1739                               if (strcmp(getcaller(), next->login) == 0)
1740                                         return 0;
1741                               else {
1742                                         if (verbose)
1743                                           rcserror("Revision %s is already locked by %s.",
1744                                                   delta->num, next->login
1745                                           );
1746                                         return -1;
1747                               }
1748                     }
1749           next = ftalloc(struct rcslock);
1750           delta->lockedby = next->login = getcaller();
1751           next->delta = delta;
1752           next->nextlock = Locks;
1753           Locks = next;
1754           return 1;
1755 }
1756 
1757 
1758           int
addsymbol(num,name,rebind)1759 addsymbol(num, name, rebind)
1760           char const *num, *name;
1761           int rebind;
1762 /*
1763  * Associate with revision NUM the new symbolic NAME.
1764  * If NAME already exists and REBIND is set, associate NAME with NUM;
1765  * otherwise, print an error message and return false;
1766  * Return -1 if unsuccessful, 0 if no change, 1 if change.
1767  */
1768 {
1769           register struct assoc *next;
1770 
1771           for (next = Symbols;  next;  next = next->nextassoc)
1772                     if (strcmp(name, next->symbol)  ==  0) {
1773                               if (strcmp(next->num,num) == 0)
1774                                         return 0;
1775                               else if (rebind) {
1776                                         next->num = num;
1777                                         return 1;
1778                               } else {
1779                                         rcserror("symbolic name %s already bound to %s",
1780                                                   name, next->num
1781                                         );
1782                                         return -1;
1783                               }
1784                     }
1785           next = ftalloc(struct assoc);
1786           next->symbol = name;
1787           next->num = num;
1788           next->nextassoc = Symbols;
1789           Symbols = next;
1790           return 1;
1791 }
1792 
1793 
1794 
1795           char const *
getcaller()1796 getcaller()
1797 /* Get the caller's login name.  */
1798 {
1799 #         if has_setuid
1800                     return getusername(euid()!=ruid());
1801 #         else
1802                     return getusername(false);
1803 #         endif
1804 }
1805 
1806 
1807           int
checkaccesslist()1808 checkaccesslist()
1809 /*
1810  * Return true if caller is the superuser, the owner of the
1811  * file, the access list is empty, or caller is on the access list.
1812  * Otherwise, print an error message and return false.
1813  */
1814 {
1815           register struct access const *next;
1816 
1817           if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
1818                     return true;
1819 
1820           next = AccessList;
1821           do {
1822                     if (strcmp(getcaller(), next->login)  ==  0)
1823                               return true;
1824           } while ((next = next->nextaccess));
1825 
1826           rcserror("user %s not on the access list", getcaller());
1827           return false;
1828 }
1829 
1830 
1831           int
dorewrite(lockflag,changed)1832 dorewrite(lockflag, changed)
1833           int lockflag, changed;
1834 /*
1835  * Do nothing if LOCKFLAG is zero.
1836  * Prepare to rewrite an RCS file if CHANGED is positive.
1837  * Stop rewriting if CHANGED is zero, because there won't be any changes.
1838  * Fail if CHANGED is negative.
1839  * Return 0 on success, -1 on failure.
1840  */
1841 {
1842           int r = 0, e;
1843 
1844           if (lockflag) {
1845                     if (changed) {
1846                               if (changed < 0)
1847                                         return -1;
1848                               putadmin();
1849                               puttree(Head, frewrite);
1850                               aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
1851                               foutptr = frewrite;
1852                     } else {
1853 #                             if bad_creat0
1854                                         int nr = !!frewrite, ne = 0;
1855 #                             endif
1856                               ORCSclose();
1857                               seteid();
1858                               ignoreints();
1859 #                             if bad_creat0
1860                                         if (nr) {
1861                                                   nr = un_link(newRCSname);
1862                                                   ne = errno;
1863                                                   keepdirtemp(newRCSname);
1864                                         }
1865 #                             endif
1866                               r = un_link(lockname);
1867                               e = errno;
1868                               keepdirtemp(lockname);
1869                               restoreints();
1870                               setrid();
1871                               if (r != 0)
1872                                         enerror(e, lockname);
1873 #                             if bad_creat0
1874                                         if (nr != 0) {
1875                                                   enerror(ne, newRCSname);
1876                                                   r = -1;
1877                                         }
1878 #                             endif
1879                     }
1880           }
1881           return r;
1882 }
1883 
1884           int
donerewrite(changed,newRCStime)1885 donerewrite(changed, newRCStime)
1886           int changed;
1887           time_t newRCStime;
1888 /*
1889  * Finish rewriting an RCS file if CHANGED is nonzero.
1890  * Set its mode if CHANGED is positive.
1891  * Set its modification time to NEWRCSTIME unless it is -1.
1892  * Return 0 on success, -1 on failure.
1893  */
1894 {
1895           int r = 0, e = 0;
1896 #         if bad_creat0
1897                     int lr, le;
1898 #         endif
1899 
1900           if (changed && !nerror) {
1901                     if (finptr) {
1902                               fastcopy(finptr, frewrite);
1903                               Izclose(&finptr);
1904                     }
1905                     if (1 < RCSstat.st_nlink)
1906                               rcswarn("breaking hard link");
1907                     aflush(frewrite);
1908                     seteid();
1909                     ignoreints();
1910                     r = chnamemod(
1911                               &frewrite, newRCSname, RCSname, changed,
1912                               RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
1913                               newRCStime
1914                     );
1915                     e = errno;
1916                     keepdirtemp(newRCSname);
1917 #                   if bad_creat0
1918                               lr = un_link(lockname);
1919                               le = errno;
1920                               keepdirtemp(lockname);
1921 #                   endif
1922                     restoreints();
1923                     setrid();
1924                     if (r != 0) {
1925                               enerror(e, RCSname);
1926                               error("saved in %s", newRCSname);
1927                     }
1928 #                   if bad_creat0
1929                               if (lr != 0) {
1930                                         enerror(le, lockname);
1931                                         r = -1;
1932                               }
1933 #                   endif
1934           }
1935           return r;
1936 }
1937 
1938           void
ORCSclose()1939 ORCSclose()
1940 {
1941           if (0 <= fdlock) {
1942                     if (close(fdlock) != 0)
1943                               efaterror(lockname);
1944                     fdlock = -1;
1945           }
1946           Ozclose(&frewrite);
1947 }
1948 
1949           void
ORCSerror()1950 ORCSerror()
1951 /*
1952 * Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
1953 * Do not report errors, since this may loop.  This is needed only because
1954 * some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
1955 * some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
1956 * This isn't a completely reliable away to work around brain-damaged hosts,
1957 * because of the gap between actual file opening and setting frewrite etc.,
1958 * but it's better than nothing.
1959 */
1960 {
1961           if (0 <= fdlock)
1962                     VOID close(fdlock);
1963           if (frewrite)
1964                     /* Avoid fclose, since stdio may not be reentrant.  */
1965                     VOID close(fileno(frewrite));
1966 }
1967