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