1 /* RCS stream editor */
3 /******************************************************************************
4 * edits the input file according to a
5 * script from stdin, generated by diff -n
6 * performs keyword expansion
7 ******************************************************************************
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.
14 This file is part of RCS.
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)
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.
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.
31 Report problems and direct all questions to:
33 rcs-bugs@cs.purdue.edu
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 $
41 * Revision 5.19 1995/06/16 06:19:24 eggert
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.
51 * (edit_string): Fix bug: if !large_memory, a bogus trailing `@' was output
52 * at the end of incomplete lines.
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.
58 * (rcswriteopen): Use fdSafer to get safer file descriptor.
59 * Open RCS file with FOPEN_RB.
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.
64 * (aflush): Move to rcslex.c.
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.
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.
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 _.
79 * Revision 5.14 1992/02/17 23:02:24 eggert
82 * Revision 5.13 1992/01/24 18:44:19 eggert
83 * Add support for bad_chmod_close, bad_creat0.
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;
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.
92 * Revision 5.10 1991/10/07 17:32:46 eggert
93 * Support piece tables even if !has_mmap. Fix rare NFS bugs.
95 * Revision 5.9 1991/09/17 19:07:40 eggert
96 * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
98 * Revision 5.8 1991/08/19 03:13:55 eggert
99 * Add piece tables, NFS bug workarounds. Catch odd filenames. Tune.
101 * Revision 5.7 1991/04/21 11:58:21 eggert
102 * Fix errno bugs. Add -x, RCSINIT, MS-DOS support.
104 * Revision 5.6 1991/02/25 07:12:40 eggert
105 * Fix setuid bug. Support new link behavior. Work around broken "w+" fopen.
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).
110 * Revision 5.4 1990/11/01 05:03:40 eggert
111 * Permit arbitrary data in comment leaders.
113 * Revision 5.3 1990/09/11 02:41:13 eggert
116 * Revision 5.2 1990/09/04 08:02:21 eggert
117 * Count RCS lines better. Improve incomplete line handling.
119 * Revision 5.1 1990/08/29 07:13:56 eggert
121 * Fix bug when getting revisions to files ending in incomplete lines.
122 * Fix bug in comment leader expansion.
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.
133 * Revision 4.8 89/05/01 15:12:35 narten
134 * changed copyright header to reflect current distribution rules
136 * Revision 4.7 88/11/08 13:54:14 narten
137 * misplaced semicolon caused infinite loop
139 * Revision 4.6 88/08/09 19:12:45 eggert
140 * Shrink stdio code size; allow cc -R.
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.
147 * Revision 4.4 87/10/18 10:32:21 narten
148 * Updating version numbers. Changes relative to version 1.1 actually
151 * Revision 1.4 87/09/24 13:59:29 narten
152 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
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
160 * fix attributed to Roy Morris @FileNet Corp ...!felix!roy
162 * Revision 1.2 87/03/27 14:22:17 jenkins
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.
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.
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@@.
179 * Revision 3.5 82/12/04 13:20:56 wft
180 * Added expansion of keyword Locker.
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.
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.
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().
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.
207 libId(editId, "$DragonFly: src/gnu/usr.bin/rcs/lib/rcsedit.c,v 1.2 2003/06/17 04:25:47 dillon Exp $")
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));
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? */
218 static RILE *fedit; /* edit file descriptor */
219 static char const *editname; /* edit pathname */
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 */
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)
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)
239 #if has_NFS || bad_unlink
244 * Remove S, even if it is unwritable.
245 * Ignore unlink() ENOENT failures; NFS generates bogus ones.
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.
258 if (chmod(s, S_IWUSR) != 0) {
265 return unlink(s)==0 || errno==ENOENT ? 0 : -1;
274 # define do_link(s,t) link(s,t)
276 static int do_link P((char const*,char const*));
280 /* Link S to T, ignoring bogus EEXIST problems due to NFS failures. */
284 if (r != 0 && errno == EEXIST) {
301 editEndsPrematurely()
303 fatserror("edit script ends prematurely");
307 editLineNumberOverflow()
309 fatserror("edit script refers to line past end of file");
316 # define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
318 static void movelines P((Iptr_type*,Iptr_type const*,long));
321 register Iptr_type *s1;
322 register Iptr_type const *s2;
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));
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.
350 * Any @s in lines are duplicated.
351 * Lines are terminated by \n, or (for a last partial line only) by single @.
353 static Iptr_type *line;
354 static size_t gap, gapsize, linelim;
360 /* Before line N, insert line L. N is 0-origin. */
362 if (linelim-gapsize < n)
363 editLineNumberOverflow();
367 tnalloc(Iptr_type, linelim = gapsize = 1024)
369 gap = gapsize = linelim,
370 trealloc(Iptr_type, line, linelim <<= 1)
373 movelines(line+n+gapsize, line+n, gap-n);
375 movelines(line+gap, line+gap+gapsize, n-gap);
383 deletelines(n, nlines)
385 /* Delete lines N through N+NLINES-1. N is 0-origin. */
388 if (linelim-gapsize < l || l < n)
389 editLineNumberOverflow();
391 movelines(line+l+gapsize, line+l, gap-l);
393 movelines(line+gap, line+gap+gapsize, n-gap);
402 register Iptr_type l;
406 if ((c = *l++) == SDELIM && *l++ != SDELIM)
415 /* Copy the current state of the edits to F. */
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++);
425 finisheditline(fin, fout, l, delta)
429 struct hshentry const *delta;
432 if (expandline(fin, fout, delta, true, (FILE*)0, true) < 0)
433 faterror("finisheditline internal error");
437 finishedit(delta, outfile, done)
438 struct hshentry const *delta;
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).
450 snapshotedit(outfile);
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);
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*));
469 fopen_update_truncate(name)
472 if (bad_fopen_wplus && un_link(name) != 0)
474 return fopenSafer(name, FOPEN_WPLUS_WORK);
485 resultname = maketemp(2);
486 if (!(fcopy = fopen_update_truncate(resultname)))
487 efaterror(resultname);
494 static void swapeditfiles P((FILE*));
496 swapeditfiles(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.
505 editline = 0; linecorr = 0;
508 tmpptr=editname; editname=resultname; resultname=tmpptr;
515 /* Copy the current state of the edits to F. */
517 finishedit((struct hshentry *)0, (FILE*)0, false);
523 finishedit(delta, outfile, done)
524 struct hshentry const *delta;
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.
539 while (1 < expandline(fe,fc,delta,false,(FILE*)0,true))
547 swapeditfiles(outfile);
554 # define copylines(upto,delta) (editline = (upto))
556 static void copylines P((long,struct hshentry const*));
558 copylines(upto, delta)
560 struct hshentry const *delta;
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.
572 if (upto < editline) {
574 finishedit((struct hshentry *)0, (FILE*)0, false);
575 /* assumes edit only during last pass, from the beginning*/
582 if (expandline(fe,fc,delta,false,(FILE*)0,true) <= 1)
583 editLineNumberOverflow();
584 } while (++editline < upto);
586 setupcache(fe); cache(fe);
589 cachegeteof_(c, editLineNumberOverflow();)
592 } while (++editline < upto);
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.
609 while (1 < expandline(finptr,fcopy,delta,true,foutptr,true))
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.
624 register FILE *frew, *fcop;
625 register int amidline;
629 setupcache(fin); cache(fin);
646 editline += amidline;
662 /* Like copystring, except the string is put into the edit data structure. */
667 editline = linecorr = 0;
668 resultname = maketemp(1);
669 if (!(fcopy = fopen_update_truncate(resultname)))
670 efaterror(resultname);
677 register int amidline, oamidline;
678 register Iptr_type optr;
685 setupcache(fin); cache(fin);
686 advise_access(fin, MADV_NORMAL);
692 oamidline = amidline;
705 editline = e + amidline;
716 insertline(oe, optr);
729 struct hshentry const *delta;
732 * Read an edit script from finptr and applies it to the edit file.
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.
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.
746 int ed; /* editor command */
752 long line_lim = LONG_MAX;
762 editline += linecorr; linecorr=0; /*correct line number*/
767 while (0 <= (ed = getdiffcmd(fin,true,frew,&dc)))
769 if (line_lim <= dc.line1)
770 editLineNumberOverflow();
774 copylines(dc.line1-1, delta);
775 /* skip over unwanted lines */
780 deletelines(editline+linecorr, i);
786 Igeteof_(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } )
791 /* Copy lines without deleting any. */
792 copylines(dc.line1, delta);
795 j = editline+linecorr;
802 switch (expandline(fin,f,delta,true,frew,true)){
808 editEndsPrematurely();
817 insertline(j++, cacheptr());
825 editEndsPrematurely();
846 /* The rest is for keyword expansion */
851 expandline(infile, outfile, delta, delimstuffed, frewfile, dolog)
853 FILE *outfile, *frewfile;
854 struct hshentry const *delta;
855 int delimstuffed, dolog;
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.
869 register FILE *out, *frew;
871 register int e, ds, r;
873 static struct buf keyval;
874 enum markers matchresult;
876 setupcache(infile); cache(infile);
880 bufalloc(&keyval, keylength+3);
888 cachegeteof_(c, goto uncache_exit;)
914 /* check for keyword */
915 /* first, copy a long enough string into keystring */
922 cachegeteof_(c, goto keystring_eof;)
923 if (tp <= &keyval.string[keylength])
925 case LETTER: case Letter:
933 *tp++ = c; *tp = '\0';
934 matchresult = trymatch(keyval.string+1);
935 if (matchresult==Nomatch) {
937 aputs(keyval.string, out);
938 continue; /* last c handled properly */
941 /* Now we have a keyword terminated with a K/VDELIM */
943 /* try to find closing KDELIM, and replace value */
944 tlim = keyval.string + keyval.size;
949 cachegeteof_(c, goto keystring_eof;)
950 if (c=='\n' || c==KDELIM)
954 tp = bufenlarge(&keyval, &tlim);
955 if (c==SDELIM && ds) { /*skip next SDELIM */
958 /* end of string before closing KDELIM or newline */
965 /* couldn't find closing KDELIM -- give up */
967 aputs(keyval.string, out);
968 continue; /* last c handled properly */
971 /* now put out the new keyword value */
973 keyreplace(matchresult, delta, ds, infile, out, dolog);
984 aputs(keyval.string, out);
992 escape_string(out, s)
994 register char const *s;
995 /* Output to OUT the string S, escaping chars that would break `ci -k'. */
999 switch ((c = *s++)) {
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;}
1007 default: aputc_(c, out) break;
1011 char const ciklog[ciklogsize] = "checked in with -k by ";
1014 keyreplace(marker, delta, delimstuffed, infile, out, dolog)
1015 enum markers marker;
1016 register struct hshentry const *delta;
1021 /* function: outputs the keyword value(s) corresponding to marker.
1022 * Attributes are derived from delta.
1025 register char const *sp, *cp, *date;
1027 register size_t cs, cw, ls;
1029 char datebuf[datesize + zonelenmax];
1033 sp = Keyword[(int)marker];
1038 if (exp != VAL_EXPAND)
1039 aprintf(out, "%c%s", KDELIM, sp);
1040 if (exp != KEY_EXPAND) {
1042 if (exp != VAL_EXPAND)
1043 aprintf(out, "%c%c", VDELIM,
1044 marker==Log && RCSv<VERSION(5) ? '\t' : ' '
1049 aputs(delta->author, out);
1052 aputs(date2str(date,datebuf), out);
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());
1065 escape_string(out, getfullRCSname());
1066 aprintf(out, " %s %s %s %s",
1068 date2str(date, datebuf),
1070 RCSv==VERSION(3) && delta->lockedby ? "Locked"
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);
1081 if (delta->lockedby)
1084 || exp == KEYVALLOCK_EXPAND
1085 || RCSv <= VERSION(4)
1087 aputs(delta->lockedby, out);
1091 escape_string(out, basefilename(RCSname));
1095 aputs(delta->name, out);
1098 aputs(delta->num, out);
1101 escape_string(out, getfullRCSname());
1104 aputs(delta->state, out);
1109 if (exp != VAL_EXPAND)
1112 if (exp != VAL_EXPAND)
1113 afputc(KDELIM, out);
1115 if (marker == Log && dolog) {
1118 sp = delta->log.string;
1119 ls = delta->log.size;
1120 if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
1122 bufautobegin(&leader);
1123 if (RCSversion < VERSION(5)) {
1124 cp = Comment.string;
1127 int kdelim_found = 0;
1128 Ioffset_type chars_read = Itell(infile);
1130 setupcache(infile); cache(infile);
1132 c = 0; /* Pacify `gcc -Wall'. */
1135 * Back up to the start of the current input line,
1136 * setting CS to the number of characters before `$Log'.
1141 goto done_backing_up;
1142 cacheunget_(infile, c)
1145 if (c == SDELIM && delimstuffed) {
1148 cacheunget_(infile, c)
1155 kdelim_found |= c==KDELIM;
1160 /* Copy characters before `$Log' into LEADER. */
1161 bufalloc(&leader, cs);
1163 for (cw = 0; cw < cs; cw++) {
1164 leader.string[cw] = c;
1165 if (c == SDELIM && delimstuffed)
1170 /* Convert traditional C or Pascal leader to ` *'. */
1171 for (cw = 0; cw < cs; cw++)
1172 if (ctab[(unsigned char) cp[cw]] != SPACE)
1177 && (cp[cw] == '/' || cp[cw] == '(')
1183 "`%c* $Log' is obsolescent; use ` * $Log'.",
1186 leader.string[cw] = ' ';
1188 } else if (ctab[(unsigned char) cp[i]] != SPACE)
1192 /* Skip `$Log ... $' string. */
1195 } while (c != KDELIM);
1199 awrite(cp, cs, out);
1200 sp1 = date2str(date, datebuf);
1201 if (VERSION(5) <= RCSv) {
1202 aprintf(out, "Revision %s %s %s",
1203 delta->num, sp1, delta->author
1206 /* oddity: 2 spaces between date and time, not 1 as usual */
1207 sp1 = strchr(sp1, ' ');
1208 aprintf(out, "Revision %s %.*s %s %s",
1209 delta->num, (int)(sp1-datebuf), datebuf, sp1,
1213 /* Do not include state: it may change and is not updated. */
1215 if (VERSION(5) <= RCSv)
1216 for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw)
1220 awrite(cp, cw, out);
1226 awrite(cp+cw, cs-cw, out);
1233 } while (c != '\n');
1236 bufautoend(&leader);
1241 static int resolve_symlink P((struct buf*));
1246 * If L is a symbolic link, resolve it to the name that it points to.
1247 * If unsuccessful, set errno and yield -1.
1248 * If it points to an existing file, yield 1.
1249 * Otherwise, set errno=ENOENT and yield 0.
1252 char *b, a[SIZEABLE_PATH];
1257 int linkcount = MAXSYMLINKS;
1261 bufautobegin(&bigbuf);
1262 while ((r = readlink(L->string,b,s)) != -1)
1264 bufalloc(&bigbuf, s<<1);
1267 } else if (!linkcount--) {
1270 * Some pedantic Posix 1003.1-1990 hosts have readlink
1271 * but not ELOOP. Approximate ELOOP with EMLINK.
1273 # define ELOOP EMLINK
1278 /* Splice symbolic link into L. */
1281 ROOTPATH(b) ? 0 : basefilename(L->string) - L->string
1286 bufautoend(&bigbuf);
1289 case readlink_isreg_errno: return 1;
1290 case ENOENT: return 0;
1297 rcswriteopen(RCSbuf, status, mustread)
1299 struct stat *status;
1302 * Create the lock file corresponding to RCSBUF.
1303 * Then try to open RCSBUF for reading and yield its RILE* descriptor.
1304 * Put its status into *STATUS too.
1305 * MUSTREAD is true if the file must already exist, too.
1306 * If all goes well, discard any previously acquired locks,
1307 * and set fdlock to the file descriptor of the RCS lockfile.
1311 register char const *sp, *RCSpath, *x;
1314 int e, exists, fdesc, fdescSafer, r, waslocked;
1316 struct stat statbuf;
1318 waslocked = 0 <= fdlock;
1321 resolve_symlink(RCSbuf);
1323 stat(RCSbuf->string, &statbuf) == 0 ? 1
1324 : errno==ENOENT ? 0 : -1;
1326 if (exists < (mustread|waslocked))
1328 * There's an unusual problem with the RCS file;
1329 * or the RCS file doesn't exist,
1330 * and we must read or we already have a lock elsewhere.
1334 RCSpath = RCSbuf->string;
1335 sp = basefilename(RCSpath);
1337 dirt = &dirtpname[waslocked];
1338 bufscpy(dirt, RCSpath);
1339 tp = dirt->string + l;
1340 x = rcssuffix(RCSpath);
1343 error("symbolic link to non RCS file `%s'", RCSpath);
1349 error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
1353 /* Create a lock filename that is a function of the RCS filename. */
1356 * The suffix is nonempty.
1357 * The lock filename is the first char of of the suffix,
1358 * followed by the RCS filename with last char removed. E.g.:
1359 * foo,v RCS filename with suffix ,v
1360 * ,foo, lock filename
1368 * The suffix is empty.
1369 * The lock filename is the RCS filename
1370 * with last char replaced by '_'.
1372 while ((*tp++ = *sp++))
1376 error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
1389 * open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
1390 * is atomic according to Posix 1003.1-1990.
1392 * NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
1394 * (O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity
1397 * If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
1398 * guarantee atomicity.
1400 * Root-over-the-wire NFS access is rare for security reasons.
1401 * This bug has never been reported in practice with RCS.
1402 * So we don't worry about this bug.
1404 * An even rarer NFS bug can occur when clients retry requests.
1405 * This can happen in the usual case of NFS over UDP.
1406 * Suppose client A releases a lock by renaming ",f," to "f,v" at
1407 * about the same time that client B obtains a lock by creating ",f,",
1408 * and suppose A's first rename request is delayed, so A reissues it.
1409 * The sequence of events might be:
1410 * A sends rename(",f,", "f,v")
1411 * B sends create(",f,")
1412 * A sends retry of rename(",f,", "f,v")
1413 * server receives, does, and acknowledges A's first rename()
1414 * A receives acknowledgment, and its RCS program exits
1415 * server receives, does, and acknowledges B's create()
1416 * server receives, does, and acknowledges A's retry of rename()
1417 * This not only wrongly deletes B's lock, it removes the RCS file!
1418 * Most NFS implementations have idempotency caches that usually prevent
1419 * this scenario, but such caches are finite and can be overrun.
1420 * This problem afflicts not only RCS, which uses open() and rename()
1421 * to get and release locks; it also afflicts the traditional
1422 * Unix method of using link() and unlink() to get and release locks,
1423 * and the less traditional method of using mkdir() and rmdir().
1424 * There is no easy workaround.
1425 * Any new method based on lockf() seemingly would be incompatible with
1426 * the old methods; besides, lockf() is notoriously buggy under NFS.
1427 * Since this problem afflicts scads of Unix programs, but is so rare
1428 * that nobody seems to be worried about it, we won't worry either.
1430 # if !open_can_creat
1431 # define create(f) creat(f, OPEN_CREAT_READONLY)
1433 # define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
1440 * Create a lock file for an RCS file. This should be atomic, i.e.
1441 * if two processes try it simultaneously, at most one should succeed.
1445 fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr. */
1450 dirtpmaker[0] = effective;
1452 if (fdescSafer < 0) {
1453 if (e == EACCES && stat(sp,&statbuf) == 0)
1454 /* The RCS file is busy. */
1459 f = Iopen(RCSpath, FOPEN_RB, status);
1461 if (f && waslocked) {
1462 /* Discard the previous lock in favor of this one. */
1465 r = un_link(lockname);
1469 enfaterror(e, lockname);
1470 bufscpy(&dirtpname[lockdirtp_index], sp);
1473 fdlock = fdescSafer;
1485 /* Do not unlink name, either because it's not there any more,
1486 * or because it has already been unlinked.
1490 for (i=DIRTEMPNAMES; 0<=--i; )
1491 if (dirtpname[i].string == name) {
1492 dirtpmaker[i] = notmade;
1495 faterror("keepdirtemp");
1499 makedirtemp(isworkfile)
1502 * Create a unique pathname and store it into dirtpname.
1503 * Because of storage in tpnames, dirtempunlink() can unlink the file later.
1504 * Return a pointer to the pathname created.
1505 * If ISWORKFILE is 1, put it into the working file's directory;
1506 * if 0, put the unique file in RCSfile's directory.
1509 register char *tp, *np;
1511 register struct buf *bn;
1512 register char const *name = isworkfile ? workname : RCSname;
1517 dl = basefilename(name) - name;
1518 bn = &dirtpname[newRCSdirtp_index + isworkfile];
1527 np = tp = bn->string;
1530 *tp++ = '0'+isworkfile;
1533 VOID strcpy(tp, "XXXXXX");
1536 faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
1537 (int)dl, name, '0'+isworkfile
1542 * Posix 1003.1-1990 has no reliable way
1543 * to create a unique file in a named directory.
1544 * We fudge here. If the filename is abcde,
1545 * the temp filename is _Ncde where N is a digit.
1550 VOID strcpy(tp, name);
1552 dirtpmaker[newRCSdirtp_index + isworkfile] = real;
1558 /* Clean up makedirtemp() files. May be invoked by signal handler. */
1563 for (i = DIRTEMPNAMES; 0 <= --i; )
1564 if ((m = dirtpmaker[i]) != notmade) {
1567 VOID un_link(dirtpname[i].string);
1570 dirtpmaker[i] = notmade;
1578 FILE **fromp, char const *from, char const *to,
1579 int set_mode, mode_t mode, time_t mtime
1581 /* The `#if has_prototypes' is needed because mode_t might promote to int. */
1583 chnamemod(fromp, from, to, set_mode, mode, mtime)
1584 FILE **fromp; char const *from,*to;
1585 int set_mode; mode_t mode; time_t mtime;
1588 * Rename a file (with stream pointer *FROMP) from FROM to TO.
1589 * FROM already exists.
1590 * If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
1591 * If MTIME is not -1, change its mtime to MTIME before renaming.
1592 * Close and clear *FROMP before renaming it.
1593 * Unlink TO if it already exists.
1594 * Return -1 on error (setting errno), 0 otherwise.
1597 mode_t mode_while_renaming = mode;
1598 int fchmod_set_mode = 0;
1600 # if bad_a_rename || bad_NFS_rename
1602 if (bad_NFS_rename || (bad_a_rename && set_mode <= 0)) {
1603 if (fstat(fileno(*fromp), &st) != 0)
1605 if (bad_a_rename && set_mode <= 0)
1612 * There's a short window of inconsistency
1613 * during which the lock file is writable.
1615 mode_while_renaming = mode|S_IWUSR;
1616 if (mode != mode_while_renaming)
1621 if (0<set_mode && fchmod(fileno(*fromp),mode_while_renaming) == 0)
1622 fchmod_set_mode = set_mode;
1624 /* If bad_chmod_close, we must close before chmod. */
1626 if (fchmod_set_mode<set_mode && chmod(from, mode_while_renaming) != 0)
1629 if (setmtime(from, mtime) != 0)
1632 # if !has_rename || bad_b_rename
1634 * There's a short window of inconsistency
1635 * during which TO does not exist.
1637 if (un_link(to) != 0 && errno != ENOENT)
1642 if (rename(from,to) != 0 && !(has_NFS && errno==ENOENT))
1645 if (do_link(from,to) != 0 || un_link(from) != 0)
1652 * Check whether the rename falsely reported success.
1653 * A race condition can occur between the rename and the stat.
1656 if (stat(to, &tostat) != 0)
1658 if (! same_file(st, tostat, 0)) {
1666 if (0 < set_mode && chmod(to, mode) != 0)
1674 setmtime(file, mtime)
1677 /* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1. */
1679 static struct utimbuf amtime; /* static so unused fields are zero */
1682 amtime.actime = now();
1683 amtime.modtime = mtime;
1684 return utime(file, &amtime);
1690 findlock(delete, target)
1692 struct hshentry **target;
1694 * Find the first lock held by caller and return a pointer
1695 * to the locked delta; also removes the lock if DELETE.
1696 * If one lock, put it into *TARGET.
1697 * Return 0 for no locks, 1 for one, 2 for two or more.
1700 register struct rcslock *next, **trail, **found;
1703 for (trail = &Locks; (next = *trail); trail = &next->nextlock)
1704 if (strcmp(getcaller(), next->login) == 0) {
1706 rcserror("multiple revisions locked by %s; please specify one", getcaller());
1714 *target = next->delta;
1716 next->delta->lockedby = 0;
1717 *found = next->nextlock;
1723 addlock(delta, verbose)
1724 struct hshentry * delta;
1727 * Add a lock held by caller to DELTA and yield 1 if successful.
1728 * Print an error message if verbose and yield -1 if no lock is added because
1729 * DELTA is locked by somebody other than caller.
1730 * Return 0 if the caller already holds the lock.
1733 register struct rcslock *next;
1735 for (next = Locks; next; next = next->nextlock)
1736 if (cmpnum(delta->num, next->delta->num) == 0)
1737 if (strcmp(getcaller(), next->login) == 0)
1741 rcserror("Revision %s is already locked by %s.",
1742 delta->num, next->login
1746 next = ftalloc(struct rcslock);
1747 delta->lockedby = next->login = getcaller();
1748 next->delta = delta;
1749 next->nextlock = Locks;
1756 addsymbol(num, name, rebind)
1757 char const *num, *name;
1760 * Associate with revision NUM the new symbolic NAME.
1761 * If NAME already exists and REBIND is set, associate NAME with NUM;
1762 * otherwise, print an error message and return false;
1763 * Return -1 if unsuccessful, 0 if no change, 1 if change.
1766 register struct assoc *next;
1768 for (next = Symbols; next; next = next->nextassoc)
1769 if (strcmp(name, next->symbol) == 0)
1770 if (strcmp(next->num,num) == 0)
1776 rcserror("symbolic name %s already bound to %s",
1781 next = ftalloc(struct assoc);
1782 next->symbol = name;
1784 next->nextassoc = Symbols;
1793 /* Get the caller's login name. */
1796 return getusername(euid()!=ruid());
1798 return getusername(false);
1806 * Return true if caller is the superuser, the owner of the
1807 * file, the access list is empty, or caller is on the access list.
1808 * Otherwise, print an error message and return false.
1811 register struct access const *next;
1813 if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
1818 if (strcmp(getcaller(), next->login) == 0)
1820 } while ((next = next->nextaccess));
1822 rcserror("user %s not on the access list", getcaller());
1828 dorewrite(lockflag, changed)
1829 int lockflag, changed;
1831 * Do nothing if LOCKFLAG is zero.
1832 * Prepare to rewrite an RCS file if CHANGED is positive.
1833 * Stop rewriting if CHANGED is zero, because there won't be any changes.
1834 * Fail if CHANGED is negative.
1835 * Return 0 on success, -1 on failure.
1845 puttree(Head, frewrite);
1846 aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
1850 int nr = !!frewrite, ne = 0;
1857 nr = un_link(newRCSname);
1859 keepdirtemp(newRCSname);
1862 r = un_link(lockname);
1864 keepdirtemp(lockname);
1868 enerror(e, lockname);
1871 enerror(ne, newRCSname);
1880 donerewrite(changed, newRCStime)
1884 * Finish rewriting an RCS file if CHANGED is nonzero.
1885 * Set its mode if CHANGED is positive.
1886 * Set its modification time to NEWRCSTIME unless it is -1.
1887 * Return 0 on success, -1 on failure.
1895 if (changed && !nerror) {
1897 fastcopy(finptr, frewrite);
1900 if (1 < RCSstat.st_nlink)
1901 rcswarn("breaking hard link");
1906 &frewrite, newRCSname, RCSname, changed,
1907 RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
1911 keepdirtemp(newRCSname);
1913 lr = un_link(lockname);
1915 keepdirtemp(lockname);
1920 enerror(e, RCSname);
1921 error("saved in %s", newRCSname);
1925 enerror(le, lockname);
1937 if (close(fdlock) != 0)
1938 efaterror(lockname);
1947 * Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
1948 * Do not report errors, since this may loop. This is needed only because
1949 * some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
1950 * some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
1951 * This isn't a completely reliable away to work around brain-damaged hosts,
1952 * because of the gap between actual file opening and setting frewrite etc.,
1953 * but it's better than nothing.
1959 /* Avoid fclose, since stdio may not be reentrant. */
1960 VOID close(fileno(frewrite));