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 || RCSv | 1066 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}
|