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