rcsedit.c revision 11894
111894Speter/* RCS stream editor */ 211894Speter 311894Speter/****************************************************************************** 49Sjkh * edits the input file according to a 59Sjkh * script from stdin, generated by diff -n 69Sjkh * performs keyword expansion 711894Speter ****************************************************************************** 89Sjkh */ 99Sjkh 1011894Speter/* Copyright 1982, 1988, 1989 Walter Tichy 1111894Speter Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert 129Sjkh Distributed under license by the Free Software Foundation, Inc. 139Sjkh 149SjkhThis file is part of RCS. 159Sjkh 169SjkhRCS is free software; you can redistribute it and/or modify 179Sjkhit under the terms of the GNU General Public License as published by 189Sjkhthe Free Software Foundation; either version 2, or (at your option) 199Sjkhany later version. 209Sjkh 219SjkhRCS is distributed in the hope that it will be useful, 229Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of 239SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 249SjkhGNU General Public License for more details. 259Sjkh 269SjkhYou should have received a copy of the GNU General Public License 2711894Speteralong with RCS; see the file COPYING. 2811894SpeterIf not, write to the Free Software Foundation, 2911894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 309Sjkh 319SjkhReport problems and direct all questions to: 329Sjkh 339Sjkh rcs-bugs@cs.purdue.edu 349Sjkh 359Sjkh*/ 369Sjkh 3711894Speter/* 3811894Speter * $Log: rcsedit.c,v $ 3911894Speter * Revision 5.19 1995/06/16 06:19:24 eggert 4011894Speter * Update FSF address. 418858Srgrimes * 4211894Speter * Revision 5.18 1995/06/01 16:23:43 eggert 4311894Speter * (dirtpname): No longer external. 4411894Speter * (do_link): Simplify logic. 4511894Speter * (finisheditline, finishedit): Replace Iseek/Itell with what they stand for. 4611894Speter * (fopen_update_truncate): Replace `#if' with `if'. 4711894Speter * (keyreplace, makedirtemp): dirlen(x) -> basefilename(x)-x. 481494Srgrimes * 4911894Speter * (edit_string): Fix bug: if !large_memory, a bogus trailing `@' was output 5011894Speter * at the end of incomplete lines. 5111894Speter * 5211894Speter * (keyreplace): Do not assume that seeking backwards 5311894Speter * at the start of a file will fail; on some systems it succeeds. 5411894Speter * Convert C- and Pascal-style comment starts to ` *' in comment leader. 5511894Speter * 5611894Speter * (rcswriteopen): Use fdSafer to get safer file descriptor. 5711894Speter * Open RCS file with FOPEN_RB. 5811894Speter * 5911894Speter * (chnamemod): Work around bad_NFS_rename bug; don't ignore un_link result. 6011894Speter * Fall back on chmod if fchmod fails, since it might be ENOSYS. 6111894Speter * 6211894Speter * (aflush): Move to rcslex.c. 6311894Speter * 6411894Speter * Revision 5.17 1994/03/20 04:52:58 eggert 6511894Speter * Normally calculate the $Log prefix from context, not from RCS file. 6611894Speter * Move setmtime here from rcsutil.c. Add ORCSerror. Remove lint. 6711894Speter * 6811894Speter * Revision 5.16 1993/11/03 17:42:27 eggert 6911894Speter * Add -z. Add Name keyword. If bad_unlink, ignore errno when unlink fails. 7011894Speter * Escape white space, $, and \ in keyword string file names. 7111894Speter * Don't output 2 spaces between date and time after Log. 7211894Speter * 7311894Speter * Revision 5.15 1992/07/28 16:12:44 eggert 7411894Speter * Some hosts have readlink but not ELOOP. Avoid `unsigned'. 7511894Speter * Preserve dates more systematically. Statement macro names now end in _. 7611894Speter * 7711894Speter * Revision 5.14 1992/02/17 23:02:24 eggert 7811894Speter * Add -T support. 7911894Speter * 8011894Speter * Revision 5.13 1992/01/24 18:44:19 eggert 8111894Speter * Add support for bad_chmod_close, bad_creat0. 8211894Speter * 8311894Speter * Revision 5.12 1992/01/06 02:42:34 eggert 8411894Speter * Add setmode parameter to chnamemod. addsymbol now reports changes. 8511894Speter * while (E) ; -> while (E) continue; 8611894Speter * 879Sjkh * Revision 5.11 1991/11/03 01:11:44 eggert 889Sjkh * Move the warning about link breaking to where they're actually being broken. 899Sjkh * 909Sjkh * Revision 5.10 1991/10/07 17:32:46 eggert 919Sjkh * Support piece tables even if !has_mmap. Fix rare NFS bugs. 929Sjkh * 939Sjkh * Revision 5.9 1991/09/17 19:07:40 eggert 949Sjkh * SGI readlink() yields ENXIO, not EINVAL, for nonlinks. 959Sjkh * 969Sjkh * Revision 5.8 1991/08/19 03:13:55 eggert 979Sjkh * Add piece tables, NFS bug workarounds. Catch odd filenames. Tune. 989Sjkh * 999Sjkh * Revision 5.7 1991/04/21 11:58:21 eggert 1009Sjkh * Fix errno bugs. Add -x, RCSINIT, MS-DOS support. 1019Sjkh * 1029Sjkh * Revision 5.6 1991/02/25 07:12:40 eggert 1039Sjkh * Fix setuid bug. Support new link behavior. Work around broken "w+" fopen. 1049Sjkh * 1059Sjkh * Revision 5.5 1990/12/30 05:07:35 eggert 1069Sjkh * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL). 1079Sjkh * 1089Sjkh * Revision 5.4 1990/11/01 05:03:40 eggert 1099Sjkh * Permit arbitrary data in comment leaders. 1109Sjkh * 1119Sjkh * Revision 5.3 1990/09/11 02:41:13 eggert 1129Sjkh * Tune expandline(). 1139Sjkh * 1149Sjkh * Revision 5.2 1990/09/04 08:02:21 eggert 1159Sjkh * Count RCS lines better. Improve incomplete line handling. 1169Sjkh * 1179Sjkh * Revision 5.1 1990/08/29 07:13:56 eggert 1189Sjkh * Add -kkvl. 1199Sjkh * Fix bug when getting revisions to files ending in incomplete lines. 1209Sjkh * Fix bug in comment leader expansion. 1219Sjkh * 1229Sjkh * Revision 5.0 1990/08/22 08:12:47 eggert 1239Sjkh * Don't require final newline. 1249Sjkh * Don't append "checked in with -k by " to logs, 1259Sjkh * so that checking in a program with -k doesn't change it. 1269Sjkh * Don't generate trailing white space for empty comment leader. 1279Sjkh * Remove compile-time limits; use malloc instead. Add -k, -V. 1289Sjkh * Permit dates past 1999/12/31. Make lock and temp files faster and safer. 1299Sjkh * Ansify and Posixate. Check diff's output. 1309Sjkh * 1319Sjkh * Revision 4.8 89/05/01 15:12:35 narten 1329Sjkh * changed copyright header to reflect current distribution rules 1338858Srgrimes * 1349Sjkh * Revision 4.7 88/11/08 13:54:14 narten 1359Sjkh * misplaced semicolon caused infinite loop 1368858Srgrimes * 1379Sjkh * Revision 4.6 88/08/09 19:12:45 eggert 1389Sjkh * Shrink stdio code size; allow cc -R. 1398858Srgrimes * 1409Sjkh * Revision 4.5 87/12/18 11:38:46 narten 1419Sjkh * Changes from the 43. version. Don't know the significance of the 1429Sjkh * first change involving "rewind". Also, additional "lint" cleanup. 1439Sjkh * (Guy Harris) 1448858Srgrimes * 1459Sjkh * Revision 4.4 87/10/18 10:32:21 narten 1469Sjkh * Updating version numbers. Changes relative to version 1.1 actually 1479Sjkh * relative to 4.1 1488858Srgrimes * 1499Sjkh * Revision 1.4 87/09/24 13:59:29 narten 1508858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf 1519Sjkh * warnings) 1528858Srgrimes * 1539Sjkh * Revision 1.3 87/09/15 16:39:39 shepler 1549Sjkh * added an initializatin of the variables editline and linecorr 1559Sjkh * this will be done each time a file is processed. 1569Sjkh * (there was an obscure bug where if co was used to retrieve multiple files 1579Sjkh * it would dump) 1589Sjkh * fix attributed to Roy Morris @FileNet Corp ...!felix!roy 1598858Srgrimes * 1609Sjkh * Revision 1.2 87/03/27 14:22:17 jenkins 1619Sjkh * Port to suns 1628858Srgrimes * 1639Sjkh * Revision 4.1 83/05/12 13:10:30 wft 1649Sjkh * Added new markers Id and RCSfile; added locker to Header and Id. 1659Sjkh * Overhauled expandline completely() (problem with $01234567890123456789@). 1669Sjkh * Moved trymatch() and marker table to rcskeys.c. 1678858Srgrimes * 1689Sjkh * Revision 3.7 83/05/12 13:04:39 wft 1699Sjkh * Added retry to expandline to resume after failed match which ended in $. 1709Sjkh * Fixed truncation problem for $19chars followed by@@. 1719Sjkh * Log no longer expands full path of RCS file. 1728858Srgrimes * 1739Sjkh * Revision 3.6 83/05/11 16:06:30 wft 1749Sjkh * added retry to expandline to resume after failed match which ended in $. 1759Sjkh * Fixed truncation problem for $19chars followed by@@. 1768858Srgrimes * 1779Sjkh * Revision 3.5 82/12/04 13:20:56 wft 1789Sjkh * Added expansion of keyword Locker. 1799Sjkh * 1809Sjkh * Revision 3.4 82/12/03 12:26:54 wft 1819Sjkh * Added line number correction in case editing does not start at the 1829Sjkh * beginning of the file. 1839Sjkh * Changed keyword expansion to always print a space before closing KDELIM; 1849Sjkh * Expansion for Header shortened. 1859Sjkh * 1869Sjkh * Revision 3.3 82/11/14 14:49:30 wft 1879Sjkh * removed Suffix from keyword expansion. Replaced fclose with ffclose. 1889Sjkh * keyreplace() gets log message from delta, not from curlogmsg. 1899Sjkh * fixed expression overflow in while(c=putc(GETC.... 1909Sjkh * checked nil printing. 1919Sjkh * 1929Sjkh * Revision 3.2 82/10/18 21:13:39 wft 1939Sjkh * I added checks for write errors during the co process, and renamed 1949Sjkh * expandstring() to xpandstring(). 1959Sjkh * 1969Sjkh * Revision 3.1 82/10/13 15:52:55 wft 1979Sjkh * changed type of result of getc() from char to int. 1989Sjkh * made keyword expansion loop in expandline() portable to machines 1999Sjkh * without sign-extension. 2009Sjkh */ 2019Sjkh 2029Sjkh 2039Sjkh#include "rcsbase.h" 2049Sjkh 20511894SpeterlibId(editId, "$Id: rcsedit.c,v 5.19 1995/06/16 06:19:24 eggert Exp $") 2069Sjkh 20711894Speterstatic void editEndsPrematurely P((void)) exiting; 20811894Speterstatic void editLineNumberOverflow P((void)) exiting; 20911894Speterstatic void escape_string P((FILE*,char const*)); 21011894Speterstatic void keyreplace P((enum markers,struct hshentry const*,int,RILE*,FILE*,int)); 2119Sjkh 2129SjkhFILE *fcopy; /* result file descriptor */ 21311894Speterchar const *resultname; /* result pathname */ 2149Sjkhint locker_expansion; /* should the locker name be appended to Id val? */ 2159Sjkh#if !large_memory 2169Sjkh static RILE *fedit; /* edit file descriptor */ 21711894Speter static char const *editname; /* edit pathname */ 2189Sjkh#endif 21911894Speterstatic long editline; /* edit line counter; #lines before cursor */ 2209Sjkhstatic long linecorr; /* #adds - #deletes in each edit run. */ 2219Sjkh /*used to correct editline in case file is not rewound after */ 2229Sjkh /* applying one delta */ 2239Sjkh 22411894Speter/* indexes into dirtpname */ 22511894Speter#define lockdirtp_index 0 22611894Speter#define newRCSdirtp_index bad_creat0 22711894Speter#define newworkdirtp_index (newRCSdirtp_index+1) 22811894Speter#define DIRTEMPNAMES (newworkdirtp_index + 1) 22911894Speter 2309Sjkhenum maker {notmade, real, effective}; 23111894Speterstatic struct buf dirtpname[DIRTEMPNAMES]; /* unlink these when done */ 23211894Speterstatic enum maker volatile dirtpmaker[DIRTEMPNAMES]; /* if these are set */ 23311894Speter#define lockname (dirtpname[lockdirtp_index].string) 23411894Speter#define newRCSname (dirtpname[newRCSdirtp_index].string) 2359Sjkh 2369Sjkh 2379Sjkh#if has_NFS || bad_unlink 2389Sjkh int 2399Sjkhun_link(s) 2409Sjkh char const *s; 2419Sjkh/* 2429Sjkh * Remove S, even if it is unwritable. 2439Sjkh * Ignore unlink() ENOENT failures; NFS generates bogus ones. 2449Sjkh */ 2459Sjkh{ 2469Sjkh# if bad_unlink 2479Sjkh if (unlink(s) == 0) 2489Sjkh return 0; 24911894Speter else { 25011894Speter int e = errno; 25111894Speter /* 25211894Speter * Forge ahead even if errno == ENOENT; some completely 25311894Speter * brain-damaged hosts (e.g. PCTCP 2.2) yield ENOENT 25411894Speter * even for existing unwritable files. 25511894Speter */ 25611894Speter if (chmod(s, S_IWUSR) != 0) { 25711894Speter errno = e; 25811894Speter return -1; 25911894Speter } 2609Sjkh } 2619Sjkh# endif 2629Sjkh# if has_NFS 2639Sjkh return unlink(s)==0 || errno==ENOENT ? 0 : -1; 2649Sjkh# else 2659Sjkh return unlink(s); 2669Sjkh# endif 2679Sjkh} 2689Sjkh#endif 2699Sjkh 2709Sjkh#if !has_rename 2719Sjkh# if !has_NFS 2729Sjkh# define do_link(s,t) link(s,t) 2739Sjkh# else 27411894Speter static int do_link P((char const*,char const*)); 2759Sjkh static int 2769Sjkhdo_link(s, t) 2779Sjkh char const *s, *t; 2789Sjkh/* Link S to T, ignoring bogus EEXIST problems due to NFS failures. */ 2799Sjkh{ 28011894Speter int r = link(s, t); 2819Sjkh 28211894Speter if (r != 0 && errno == EEXIST) { 28311894Speter struct stat sb, tb; 28411894Speter if ( 28511894Speter stat(s, &sb) == 0 && 28611894Speter stat(t, &tb) == 0 && 28711894Speter same_file(sb, tb, 0) 28811894Speter ) 28911894Speter r = 0; 29011894Speter errno = EEXIST; 29111894Speter } 29211894Speter return r; 2939Sjkh} 2949Sjkh# endif 2959Sjkh#endif 2969Sjkh 2979Sjkh 29811894Speter static void 2999SjkheditEndsPrematurely() 3009Sjkh{ 3019Sjkh fatserror("edit script ends prematurely"); 3029Sjkh} 3039Sjkh 30411894Speter static void 3059SjkheditLineNumberOverflow() 3069Sjkh{ 3079Sjkh fatserror("edit script refers to line past end of file"); 3089Sjkh} 3099Sjkh 3109Sjkh 3119Sjkh#if large_memory 3129Sjkh 3139Sjkh#if has_memmove 3149Sjkh# define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type)) 3159Sjkh#else 31611894Speter static void movelines P((Iptr_type*,Iptr_type const*,long)); 3179Sjkh static void 3189Sjkhmovelines(s1, s2, n) 3199Sjkh register Iptr_type *s1; 3209Sjkh register Iptr_type const *s2; 32111894Speter register long n; 3229Sjkh{ 3239Sjkh if (s1 < s2) 3249Sjkh do { 3259Sjkh *s1++ = *s2++; 3269Sjkh } while (--n); 3279Sjkh else { 3289Sjkh s1 += n; 3299Sjkh s2 += n; 3309Sjkh do { 3319Sjkh *--s1 = *--s2; 3329Sjkh } while (--n); 3339Sjkh } 3349Sjkh} 3359Sjkh#endif 3369Sjkh 33711894Speterstatic void deletelines P((long,long)); 33811894Speterstatic void finisheditline P((RILE*,FILE*,Iptr_type,struct hshentry const*)); 33911894Speterstatic void insertline P((long,Iptr_type)); 34011894Speterstatic void snapshotline P((FILE*,Iptr_type)); 34111894Speter 3429Sjkh/* 3439Sjkh * `line' contains pointers to the lines in the currently `edited' file. 3449Sjkh * It is a 0-origin array that represents linelim-gapsize lines. 34511894Speter * line[0 .. gap-1] and line[gap+gapsize .. linelim-1] hold pointers to lines. 34611894Speter * line[gap .. gap+gapsize-1] contains garbage. 3479Sjkh * 3489Sjkh * Any @s in lines are duplicated. 3499Sjkh * Lines are terminated by \n, or (for a last partial line only) by single @. 3509Sjkh */ 3519Sjkhstatic Iptr_type *line; 35211894Speterstatic size_t gap, gapsize, linelim; 3539Sjkh 3549Sjkh static void 3559Sjkhinsertline(n, l) 35611894Speter long n; 3579Sjkh Iptr_type l; 3589Sjkh/* Before line N, insert line L. N is 0-origin. */ 3599Sjkh{ 3609Sjkh if (linelim-gapsize < n) 3619Sjkh editLineNumberOverflow(); 3629Sjkh if (!gapsize) 3639Sjkh line = 3649Sjkh !linelim ? 3659Sjkh tnalloc(Iptr_type, linelim = gapsize = 1024) 3669Sjkh : ( 3679Sjkh gap = gapsize = linelim, 3689Sjkh trealloc(Iptr_type, line, linelim <<= 1) 3699Sjkh ); 3709Sjkh if (n < gap) 3719Sjkh movelines(line+n+gapsize, line+n, gap-n); 3729Sjkh else if (gap < n) 3739Sjkh movelines(line+gap, line+gap+gapsize, n-gap); 3749Sjkh 3759Sjkh line[n] = l; 3769Sjkh gap = n + 1; 3779Sjkh gapsize--; 3789Sjkh} 3799Sjkh 3809Sjkh static void 3819Sjkhdeletelines(n, nlines) 38211894Speter long n, nlines; 3839Sjkh/* Delete lines N through N+NLINES-1. N is 0-origin. */ 3849Sjkh{ 38511894Speter long l = n + nlines; 3869Sjkh if (linelim-gapsize < l || l < n) 3879Sjkh editLineNumberOverflow(); 3889Sjkh if (l < gap) 3899Sjkh movelines(line+l+gapsize, line+l, gap-l); 3909Sjkh else if (gap < n) 3919Sjkh movelines(line+gap, line+gap+gapsize, n-gap); 3929Sjkh 3939Sjkh gap = n; 3949Sjkh gapsize += nlines; 3959Sjkh} 3969Sjkh 3979Sjkh static void 3989Sjkhsnapshotline(f, l) 3999Sjkh register FILE *f; 4009Sjkh register Iptr_type l; 4019Sjkh{ 4029Sjkh register int c; 4039Sjkh do { 4049Sjkh if ((c = *l++) == SDELIM && *l++ != SDELIM) 4059Sjkh return; 40611894Speter aputc_(c, f) 4079Sjkh } while (c != '\n'); 4089Sjkh} 4099Sjkh 4109Sjkh void 4119Sjkhsnapshotedit(f) 4129Sjkh FILE *f; 4139Sjkh/* Copy the current state of the edits to F. */ 4149Sjkh{ 4159Sjkh register Iptr_type *p, *lim, *l=line; 4169Sjkh for (p=l, lim=l+gap; p<lim; ) 4179Sjkh snapshotline(f, *p++); 4189Sjkh for (p+=gapsize, lim=l+linelim; p<lim; ) 4199Sjkh snapshotline(f, *p++); 4209Sjkh} 4219Sjkh 4229Sjkh static void 4239Sjkhfinisheditline(fin, fout, l, delta) 4249Sjkh RILE *fin; 4259Sjkh FILE *fout; 4269Sjkh Iptr_type l; 4279Sjkh struct hshentry const *delta; 4289Sjkh{ 42911894Speter fin->ptr = l; 43011894Speter if (expandline(fin, fout, delta, true, (FILE*)0, true) < 0) 4319Sjkh faterror("finisheditline internal error"); 4329Sjkh} 4339Sjkh 4349Sjkh void 4359Sjkhfinishedit(delta, outfile, done) 4369Sjkh struct hshentry const *delta; 4379Sjkh FILE *outfile; 4389Sjkh int done; 4399Sjkh/* 4409Sjkh * Doing expansion if DELTA is set, output the state of the edits to OUTFILE. 4419Sjkh * But do nothing unless DONE is set (which means we are on the last pass). 4429Sjkh */ 4439Sjkh{ 4449Sjkh if (done) { 4459Sjkh openfcopy(outfile); 4469Sjkh outfile = fcopy; 4479Sjkh if (!delta) 4489Sjkh snapshotedit(outfile); 4499Sjkh else { 4509Sjkh register Iptr_type *p, *lim, *l = line; 4519Sjkh register RILE *fin = finptr; 45211894Speter Iptr_type here = fin->ptr; 4539Sjkh for (p=l, lim=l+gap; p<lim; ) 4549Sjkh finisheditline(fin, outfile, *p++, delta); 4559Sjkh for (p+=gapsize, lim=l+linelim; p<lim; ) 4569Sjkh finisheditline(fin, outfile, *p++, delta); 45711894Speter fin->ptr = here; 4589Sjkh } 4599Sjkh } 4609Sjkh} 4619Sjkh 46211894Speter/* Open a temporary NAME for output, truncating any previous contents. */ 46311894Speter# define fopen_update_truncate(name) fopenSafer(name, FOPEN_W_WORK) 4649Sjkh#else /* !large_memory */ 46511894Speter static FILE * fopen_update_truncate P((char const*)); 4669Sjkh static FILE * 46711894Speterfopen_update_truncate(name) 46811894Speter char const *name; 4699Sjkh{ 47011894Speter if (bad_fopen_wplus && un_link(name) != 0) 47111894Speter efaterror(name); 47211894Speter return fopenSafer(name, FOPEN_WPLUS_WORK); 4739Sjkh} 4749Sjkh#endif 4759Sjkh 4769Sjkh 4779Sjkh void 4789Sjkhopenfcopy(f) 4799Sjkh FILE *f; 4809Sjkh{ 4819Sjkh if (!(fcopy = f)) { 48211894Speter if (!resultname) 48311894Speter resultname = maketemp(2); 48411894Speter if (!(fcopy = fopen_update_truncate(resultname))) 48511894Speter efaterror(resultname); 4869Sjkh } 4879Sjkh} 4889Sjkh 4899Sjkh 4909Sjkh#if !large_memory 4919Sjkh 49211894Speter static void swapeditfiles P((FILE*)); 4939Sjkh static void 4949Sjkhswapeditfiles(outfile) 4959Sjkh FILE *outfile; 49611894Speter/* Function: swaps resultname and editname, assigns fedit=fcopy, 4979Sjkh * and rewinds fedit for reading. Set fcopy to outfile if nonnull; 49811894Speter * otherwise, set fcopy to be resultname opened for reading and writing. 4999Sjkh */ 5009Sjkh{ 5019Sjkh char const *tmpptr; 5029Sjkh 5039Sjkh editline = 0; linecorr = 0; 50411894Speter Orewind(fcopy); 5059Sjkh fedit = fcopy; 50611894Speter tmpptr=editname; editname=resultname; resultname=tmpptr; 5079Sjkh openfcopy(outfile); 5089Sjkh} 5099Sjkh 5109Sjkh void 5119Sjkhsnapshotedit(f) 5129Sjkh FILE *f; 5139Sjkh/* Copy the current state of the edits to F. */ 5149Sjkh{ 51511894Speter finishedit((struct hshentry *)0, (FILE*)0, false); 5169Sjkh fastcopy(fedit, f); 5179Sjkh Irewind(fedit); 5189Sjkh} 5199Sjkh 5209Sjkh void 5219Sjkhfinishedit(delta, outfile, done) 5229Sjkh struct hshentry const *delta; 5239Sjkh FILE *outfile; 5249Sjkh int done; 5259Sjkh/* copy the rest of the edit file and close it (if it exists). 52611894Speter * if delta, perform keyword substitution at the same time. 5279Sjkh * If DONE is set, we are finishing the last pass. 5289Sjkh */ 5299Sjkh{ 5309Sjkh register RILE *fe; 5319Sjkh register FILE *fc; 5329Sjkh 5339Sjkh fe = fedit; 5349Sjkh if (fe) { 5359Sjkh fc = fcopy; 53611894Speter if (delta) { 53711894Speter while (1 < expandline(fe,fc,delta,false,(FILE*)0,true)) 5389Sjkh ; 5399Sjkh } else { 5409Sjkh fastcopy(fe,fc); 5419Sjkh } 5429Sjkh Ifclose(fe); 5439Sjkh } 5449Sjkh if (!done) 5459Sjkh swapeditfiles(outfile); 5469Sjkh} 5479Sjkh#endif 5489Sjkh 5499Sjkh 5509Sjkh 5519Sjkh#if large_memory 5529Sjkh# define copylines(upto,delta) (editline = (upto)) 5539Sjkh#else 55411894Speter static void copylines P((long,struct hshentry const*)); 5559Sjkh static void 55611894Spetercopylines(upto, delta) 55711894Speter register long upto; 5589Sjkh struct hshentry const *delta; 5599Sjkh/* 5609Sjkh * Copy input lines editline+1..upto from fedit to fcopy. 56111894Speter * If delta, keyword expansion is done simultaneously. 5629Sjkh * editline is updated. Rewinds a file only if necessary. 5639Sjkh */ 5649Sjkh{ 5659Sjkh register int c; 5669Sjkh declarecache; 5679Sjkh register FILE *fc; 5689Sjkh register RILE *fe; 5699Sjkh 5709Sjkh if (upto < editline) { 5719Sjkh /* swap files */ 57211894Speter finishedit((struct hshentry *)0, (FILE*)0, false); 5739Sjkh /* assumes edit only during last pass, from the beginning*/ 5749Sjkh } 5759Sjkh fe = fedit; 5769Sjkh fc = fcopy; 5779Sjkh if (editline < upto) 5789Sjkh if (delta) 5799Sjkh do { 58011894Speter if (expandline(fe,fc,delta,false,(FILE*)0,true) <= 1) 58111894Speter editLineNumberOverflow(); 5829Sjkh } while (++editline < upto); 5839Sjkh else { 5849Sjkh setupcache(fe); cache(fe); 5859Sjkh do { 5869Sjkh do { 58711894Speter cachegeteof_(c, editLineNumberOverflow();) 58811894Speter aputc_(c, fc) 5899Sjkh } while (c != '\n'); 5909Sjkh } while (++editline < upto); 5919Sjkh uncache(fe); 5929Sjkh } 5939Sjkh} 5949Sjkh#endif 5959Sjkh 5969Sjkh 5979Sjkh 5989Sjkh void 5999Sjkhxpandstring(delta) 6009Sjkh struct hshentry const *delta; 6019Sjkh/* Function: Reads a string terminated by SDELIM from finptr and writes it 6029Sjkh * to fcopy. Double SDELIM is replaced with single SDELIM. 6039Sjkh * Keyword expansion is performed with data from delta. 6049Sjkh * If foutptr is nonnull, the string is also copied unchanged to foutptr. 6059Sjkh */ 6069Sjkh{ 60711894Speter while (1 < expandline(finptr,fcopy,delta,true,foutptr,true)) 60811894Speter continue; 6099Sjkh} 6109Sjkh 6119Sjkh 6129Sjkh void 6139Sjkhcopystring() 6149Sjkh/* Function: copies a string terminated with a single SDELIM from finptr to 6159Sjkh * fcopy, replacing all double SDELIM with a single SDELIM. 6169Sjkh * If foutptr is nonnull, the string also copied unchanged to foutptr. 6179Sjkh * editline is incremented by the number of lines copied. 6189Sjkh * Assumption: next character read is first string character. 6199Sjkh */ 6209Sjkh{ register c; 6219Sjkh declarecache; 6229Sjkh register FILE *frew, *fcop; 6239Sjkh register int amidline; 6249Sjkh register RILE *fin; 6259Sjkh 6269Sjkh fin = finptr; 6279Sjkh setupcache(fin); cache(fin); 6289Sjkh frew = foutptr; 6299Sjkh fcop = fcopy; 6309Sjkh amidline = false; 6319Sjkh for (;;) { 63211894Speter GETC_(frew,c) 6339Sjkh switch (c) { 6349Sjkh case '\n': 6359Sjkh ++editline; 6369Sjkh ++rcsline; 6379Sjkh amidline = false; 6389Sjkh break; 6399Sjkh case SDELIM: 64011894Speter GETC_(frew,c) 6419Sjkh if (c != SDELIM) { 6429Sjkh /* end of string */ 6439Sjkh nextc = c; 6449Sjkh editline += amidline; 6459Sjkh uncache(fin); 6469Sjkh return; 6479Sjkh } 6489Sjkh /* fall into */ 6499Sjkh default: 6509Sjkh amidline = true; 6519Sjkh break; 6529Sjkh } 65311894Speter aputc_(c,fcop) 6549Sjkh } 6559Sjkh} 6569Sjkh 6579Sjkh 6589Sjkh void 6599Sjkhenterstring() 6609Sjkh/* Like copystring, except the string is put into the edit data structure. */ 6619Sjkh{ 6629Sjkh#if !large_memory 66311894Speter editname = 0; 6649Sjkh fedit = 0; 6659Sjkh editline = linecorr = 0; 66611894Speter resultname = maketemp(1); 66711894Speter if (!(fcopy = fopen_update_truncate(resultname))) 66811894Speter efaterror(resultname); 6699Sjkh copystring(); 6709Sjkh#else 6719Sjkh register int c; 6729Sjkh declarecache; 6739Sjkh register FILE *frew; 67411894Speter register long e, oe; 6759Sjkh register int amidline, oamidline; 6769Sjkh register Iptr_type optr; 6779Sjkh register RILE *fin; 6789Sjkh 6799Sjkh e = 0; 6809Sjkh gap = 0; 6819Sjkh gapsize = linelim; 6829Sjkh fin = finptr; 6839Sjkh setupcache(fin); cache(fin); 6849Sjkh advise_access(fin, MADV_NORMAL); 6859Sjkh frew = foutptr; 6869Sjkh amidline = false; 6879Sjkh for (;;) { 68811894Speter optr = cacheptr(); 68911894Speter GETC_(frew,c) 6909Sjkh oamidline = amidline; 6919Sjkh oe = e; 6929Sjkh switch (c) { 6939Sjkh case '\n': 6949Sjkh ++e; 6959Sjkh ++rcsline; 6969Sjkh amidline = false; 6979Sjkh break; 6989Sjkh case SDELIM: 69911894Speter GETC_(frew,c) 7009Sjkh if (c != SDELIM) { 7019Sjkh /* end of string */ 7029Sjkh nextc = c; 7039Sjkh editline = e + amidline; 7049Sjkh linecorr = 0; 7059Sjkh uncache(fin); 7069Sjkh return; 7079Sjkh } 7089Sjkh /* fall into */ 7099Sjkh default: 7109Sjkh amidline = true; 7119Sjkh break; 7129Sjkh } 7139Sjkh if (!oamidline) 7149Sjkh insertline(oe, optr); 7159Sjkh } 7169Sjkh#endif 7179Sjkh} 7189Sjkh 7199Sjkh 7209Sjkh 7219Sjkh 7229Sjkh void 7239Sjkh#if large_memory 7249Sjkhedit_string() 7259Sjkh#else 7269Sjkh editstring(delta) 7279Sjkh struct hshentry const *delta; 7289Sjkh#endif 7299Sjkh/* 7309Sjkh * Read an edit script from finptr and applies it to the edit file. 7319Sjkh#if !large_memory 7329Sjkh * The result is written to fcopy. 73311894Speter * If delta, keyword expansion is performed simultaneously. 7349Sjkh * If running out of lines in fedit, fedit and fcopy are swapped. 73511894Speter * editname is the name of the file that goes with fedit. 7369Sjkh#endif 7379Sjkh * If foutptr is set, the edit script is also copied verbatim to foutptr. 7389Sjkh * Assumes that all these files are open. 73911894Speter * resultname is the name of the file that goes with fcopy. 7409Sjkh * Assumes the next input character from finptr is the first character of 7419Sjkh * the edit script. Resets nextc on exit. 7429Sjkh */ 7439Sjkh{ 7449Sjkh int ed; /* editor command */ 7459Sjkh register int c; 7469Sjkh declarecache; 7479Sjkh register FILE *frew; 7489Sjkh# if !large_memory 7499Sjkh register FILE *f; 75011894Speter long line_lim = LONG_MAX; 7519Sjkh register RILE *fe; 7529Sjkh# endif 75311894Speter register long i; 7549Sjkh register RILE *fin; 7559Sjkh# if large_memory 75611894Speter register long j; 7579Sjkh# endif 7589Sjkh struct diffcmd dc; 7599Sjkh 7609Sjkh editline += linecorr; linecorr=0; /*correct line number*/ 7619Sjkh frew = foutptr; 7629Sjkh fin = finptr; 7639Sjkh setupcache(fin); 7649Sjkh initdiffcmd(&dc); 7659Sjkh while (0 <= (ed = getdiffcmd(fin,true,frew,&dc))) 7669Sjkh#if !large_memory 7679Sjkh if (line_lim <= dc.line1) 7689Sjkh editLineNumberOverflow(); 7699Sjkh else 7709Sjkh#endif 7719Sjkh if (!ed) { 7729Sjkh copylines(dc.line1-1, delta); 7739Sjkh /* skip over unwanted lines */ 7749Sjkh i = dc.nlines; 7759Sjkh linecorr -= i; 7769Sjkh editline += i; 7779Sjkh# if large_memory 7789Sjkh deletelines(editline+linecorr, i); 7799Sjkh# else 7809Sjkh fe = fedit; 7819Sjkh do { 7829Sjkh /*skip next line*/ 7839Sjkh do { 78411894Speter Igeteof_(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } ) 7859Sjkh } while (c != '\n'); 7869Sjkh } while (--i); 7879Sjkh# endif 7889Sjkh } else { 78911894Speter /* Copy lines without deleting any. */ 79011894Speter copylines(dc.line1, delta); 7919Sjkh i = dc.nlines; 7929Sjkh# if large_memory 7939Sjkh j = editline+linecorr; 7949Sjkh# endif 7959Sjkh linecorr += i; 7969Sjkh#if !large_memory 7979Sjkh f = fcopy; 7989Sjkh if (delta) 7999Sjkh do { 80011894Speter switch (expandline(fin,f,delta,true,frew,true)){ 8019Sjkh case 0: case 1: 8029Sjkh if (i==1) 8039Sjkh return; 8049Sjkh /* fall into */ 8059Sjkh case -1: 8069Sjkh editEndsPrematurely(); 8079Sjkh } 8089Sjkh } while (--i); 8099Sjkh else 8109Sjkh#endif 8119Sjkh { 8129Sjkh cache(fin); 8139Sjkh do { 8149Sjkh# if large_memory 81511894Speter insertline(j++, cacheptr()); 8169Sjkh# endif 8179Sjkh for (;;) { 81811894Speter GETC_(frew, c) 8199Sjkh if (c==SDELIM) { 82011894Speter GETC_(frew, c) 8219Sjkh if (c!=SDELIM) { 8229Sjkh if (--i) 8239Sjkh editEndsPrematurely(); 8249Sjkh nextc = c; 8259Sjkh uncache(fin); 8269Sjkh return; 8279Sjkh } 8289Sjkh } 82911894Speter# if !large_memory 83011894Speter aputc_(c, f) 83111894Speter# endif 83211894Speter if (c == '\n') 83311894Speter break; 8349Sjkh } 8359Sjkh ++rcsline; 8369Sjkh } while (--i); 8379Sjkh uncache(fin); 8389Sjkh } 8399Sjkh } 8409Sjkh} 8419Sjkh 8429Sjkh 8439Sjkh 8449Sjkh/* The rest is for keyword expansion */ 8459Sjkh 8469Sjkh 8479Sjkh 8489Sjkh int 84911894Speterexpandline(infile, outfile, delta, delimstuffed, frewfile, dolog) 8509Sjkh RILE *infile; 8519Sjkh FILE *outfile, *frewfile; 8529Sjkh struct hshentry const *delta; 85311894Speter int delimstuffed, dolog; 8549Sjkh/* 8559Sjkh * Read a line from INFILE and write it to OUTFILE. 85611894Speter * Do keyword expansion with data from DELTA. 8579Sjkh * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM. 8589Sjkh * If FREWFILE is set, copy the line unchanged to FREWFILE. 8599Sjkh * DELIMSTUFFED must be true if FREWFILE is set. 86011894Speter * Append revision history to log only if DOLOG is set. 8619Sjkh * Yields -1 if no data is copied, 0 if an incomplete line is copied, 8629Sjkh * 2 if a complete line is copied; adds 1 to yield if expansion occurred. 8639Sjkh */ 8649Sjkh{ 8659Sjkh register c; 8669Sjkh declarecache; 8679Sjkh register FILE *out, *frew; 8689Sjkh register char * tp; 8699Sjkh register int e, ds, r; 8709Sjkh char const *tlim; 8719Sjkh static struct buf keyval; 8729Sjkh enum markers matchresult; 8739Sjkh 8749Sjkh setupcache(infile); cache(infile); 8759Sjkh out = outfile; 8769Sjkh frew = frewfile; 8779Sjkh ds = delimstuffed; 8789Sjkh bufalloc(&keyval, keylength+3); 8799Sjkh e = 0; 8809Sjkh r = -1; 8819Sjkh 8829Sjkh for (;;) { 88311894Speter if (ds) 88411894Speter GETC_(frew, c) 88511894Speter else 88611894Speter cachegeteof_(c, goto uncache_exit;) 8879Sjkh for (;;) { 8889Sjkh switch (c) { 8899Sjkh case SDELIM: 8909Sjkh if (ds) { 89111894Speter GETC_(frew, c) 8929Sjkh if (c != SDELIM) { 8939Sjkh /* end of string */ 8949Sjkh nextc=c; 8959Sjkh goto uncache_exit; 8969Sjkh } 8979Sjkh } 8989Sjkh /* fall into */ 8999Sjkh default: 90011894Speter aputc_(c,out) 9019Sjkh r = 0; 9029Sjkh break; 9039Sjkh 9049Sjkh case '\n': 9059Sjkh rcsline += ds; 90611894Speter aputc_(c,out) 9079Sjkh r = 2; 9089Sjkh goto uncache_exit; 9099Sjkh 9109Sjkh case KDELIM: 9119Sjkh r = 0; 9129Sjkh /* check for keyword */ 9139Sjkh /* first, copy a long enough string into keystring */ 9149Sjkh tp = keyval.string; 9159Sjkh *tp++ = KDELIM; 9169Sjkh for (;;) { 91711894Speter if (ds) 91811894Speter GETC_(frew, c) 91911894Speter else 92011894Speter cachegeteof_(c, goto keystring_eof;) 92111894Speter if (tp <= &keyval.string[keylength]) 9229Sjkh switch (ctab[c]) { 9239Sjkh case LETTER: case Letter: 9249Sjkh *tp++ = c; 9259Sjkh continue; 9269Sjkh default: 9279Sjkh break; 9289Sjkh } 9299Sjkh break; 9309Sjkh } 9319Sjkh *tp++ = c; *tp = '\0'; 9329Sjkh matchresult = trymatch(keyval.string+1); 9339Sjkh if (matchresult==Nomatch) { 9349Sjkh tp[-1] = 0; 9359Sjkh aputs(keyval.string, out); 9369Sjkh continue; /* last c handled properly */ 9379Sjkh } 9389Sjkh 9399Sjkh /* Now we have a keyword terminated with a K/VDELIM */ 9409Sjkh if (c==VDELIM) { 9419Sjkh /* try to find closing KDELIM, and replace value */ 9429Sjkh tlim = keyval.string + keyval.size; 9439Sjkh for (;;) { 94411894Speter if (ds) 94511894Speter GETC_(frew, c) 94611894Speter else 94711894Speter cachegeteof_(c, goto keystring_eof;) 9489Sjkh if (c=='\n' || c==KDELIM) 9499Sjkh break; 9509Sjkh *tp++ =c; 9519Sjkh if (tlim <= tp) 9529Sjkh tp = bufenlarge(&keyval, &tlim); 9539Sjkh if (c==SDELIM && ds) { /*skip next SDELIM */ 95411894Speter GETC_(frew, c) 9559Sjkh if (c != SDELIM) { 9569Sjkh /* end of string before closing KDELIM or newline */ 9579Sjkh nextc = c; 9589Sjkh goto keystring_eof; 9599Sjkh } 9609Sjkh } 9619Sjkh } 9629Sjkh if (c!=KDELIM) { 9639Sjkh /* couldn't find closing KDELIM -- give up */ 9649Sjkh *tp = 0; 9659Sjkh aputs(keyval.string, out); 9669Sjkh continue; /* last c handled properly */ 9679Sjkh } 9689Sjkh } 9699Sjkh /* now put out the new keyword value */ 97011894Speter uncache(infile); 97111894Speter keyreplace(matchresult, delta, ds, infile, out, dolog); 97211894Speter cache(infile); 9739Sjkh e = 1; 9749Sjkh break; 9759Sjkh } 9769Sjkh break; 9779Sjkh } 9789Sjkh } 9799Sjkh 9809Sjkh keystring_eof: 9819Sjkh *tp = 0; 9829Sjkh aputs(keyval.string, out); 9839Sjkh uncache_exit: 9849Sjkh uncache(infile); 9859Sjkh return r + e; 9869Sjkh} 9879Sjkh 9889Sjkh 98911894Speter static void 99011894Speterescape_string(out, s) 99111894Speter register FILE *out; 99211894Speter register char const *s; 99311894Speter/* Output to OUT the string S, escaping chars that would break `ci -k'. */ 99411894Speter{ 99511894Speter register char c; 99611894Speter for (;;) 99711894Speter switch ((c = *s++)) { 99811894Speter case 0: return; 99911894Speter case '\t': aputs("\\t", out); break; 100011894Speter case '\n': aputs("\\n", out); break; 100111894Speter case ' ': aputs("\\040", out); break; 100211894Speter case KDELIM: aputs("\\044", out); break; 100311894Speter case '\\': if (VERSION(5)<=RCSversion) {aputs("\\\\", out); break;} 100411894Speter /* fall into */ 100511894Speter default: aputc_(c, out) break; 100611894Speter } 100711894Speter} 100811894Speter 10099Sjkhchar const ciklog[ciklogsize] = "checked in with -k by "; 10109Sjkh 10119Sjkh static void 101211894Speterkeyreplace(marker, delta, delimstuffed, infile, out, dolog) 10139Sjkh enum markers marker; 10149Sjkh register struct hshentry const *delta; 101511894Speter int delimstuffed; 101611894Speter RILE *infile; 10179Sjkh register FILE *out; 101811894Speter int dolog; 10199Sjkh/* function: outputs the keyword value(s) corresponding to marker. 10209Sjkh * Attributes are derived from delta. 10219Sjkh */ 10229Sjkh{ 10239Sjkh register char const *sp, *cp, *date; 102411894Speter register int c; 10259Sjkh register size_t cs, cw, ls; 10269Sjkh char const *sp1; 102711894Speter char datebuf[datesize + zonelenmax]; 10289Sjkh int RCSv; 102911894Speter int exp; 10309Sjkh 10319Sjkh sp = Keyword[(int)marker]; 103211894Speter exp = Expand; 103311894Speter date = delta->date; 103411894Speter RCSv = RCSversion; 10359Sjkh 103611894Speter if (exp != VAL_EXPAND) 103711894Speter aprintf(out, "%c%s", KDELIM, sp); 103811894Speter if (exp != KEY_EXPAND) { 10399Sjkh 104011894Speter if (exp != VAL_EXPAND) 104111894Speter aprintf(out, "%c%c", VDELIM, 10429Sjkh marker==Log && RCSv<VERSION(5) ? '\t' : ' ' 10439Sjkh ); 10449Sjkh 104511894Speter switch (marker) { 104611894Speter case Author: 10479Sjkh aputs(delta->author, out); 10489Sjkh break; 104911894Speter case Date: 10509Sjkh aputs(date2str(date,datebuf), out); 10519Sjkh break; 105211894Speter case Id: 105311894Speter case Header: 105411894Speter escape_string(out, 105511894Speter marker==Id || RCSv<VERSION(4) 105611894Speter ? basefilename(RCSname) 105711894Speter : getfullRCSname() 105811894Speter ); 105911894Speter aprintf(out, " %s %s %s %s", 10609Sjkh delta->num, 10619Sjkh date2str(date, datebuf), 10629Sjkh delta->author, 10639Sjkh RCSv==VERSION(3) && delta->lockedby ? "Locked" 10649Sjkh : delta->state 10659Sjkh ); 106611894Speter if (delta->lockedby) 10679Sjkh if (VERSION(5) <= RCSv) { 106811894Speter if (locker_expansion || exp==KEYVALLOCK_EXPAND) 10699Sjkh aprintf(out, " %s", delta->lockedby); 10709Sjkh } else if (RCSv == VERSION(4)) 10719Sjkh aprintf(out, " Locker: %s", delta->lockedby); 10729Sjkh break; 107311894Speter case Locker: 10749Sjkh if (delta->lockedby) 10759Sjkh if ( 10769Sjkh locker_expansion 107711894Speter || exp == KEYVALLOCK_EXPAND 10789Sjkh || RCSv <= VERSION(4) 10799Sjkh ) 10809Sjkh aputs(delta->lockedby, out); 10819Sjkh break; 108211894Speter case Log: 108311894Speter case RCSfile: 108411894Speter escape_string(out, basefilename(RCSname)); 10859Sjkh break; 108611894Speter case Name: 108711894Speter if (delta->name) 108811894Speter aputs(delta->name, out); 108911894Speter break; 109011894Speter case Revision: 10919Sjkh aputs(delta->num, out); 10929Sjkh break; 109311894Speter case Source: 109411894Speter escape_string(out, getfullRCSname()); 10959Sjkh break; 109611894Speter case State: 10979Sjkh aputs(delta->state, out); 10989Sjkh break; 109911894Speter default: 11009Sjkh break; 110111894Speter } 110211894Speter if (exp != VAL_EXPAND) 11039Sjkh afputc(' ', out); 11049Sjkh } 110511894Speter if (exp != VAL_EXPAND) 110611894Speter afputc(KDELIM, out); 110711894Speter 110811894Speter if (marker == Log && dolog) { 110911894Speter struct buf leader; 111011894Speter 11119Sjkh sp = delta->log.string; 11129Sjkh ls = delta->log.size; 11139Sjkh if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1)) 11149Sjkh return; 111511894Speter bufautobegin(&leader); 111611894Speter if (RCSversion < VERSION(5)) { 111711894Speter cp = Comment.string; 111811894Speter cs = Comment.size; 111911894Speter } else { 112011894Speter int kdelim_found = 0; 112111894Speter Ioffset_type chars_read = Itell(infile); 112211894Speter declarecache; 112311894Speter setupcache(infile); cache(infile); 112411894Speter 112511894Speter c = 0; /* Pacify `gcc -Wall'. */ 112611894Speter 112711894Speter /* 112811894Speter * Back up to the start of the current input line, 112911894Speter * setting CS to the number of characters before `$Log'. 113011894Speter */ 113111894Speter cs = 0; 113211894Speter for (;;) { 113311894Speter if (!--chars_read) 113411894Speter goto done_backing_up; 113511894Speter cacheunget_(infile, c) 113611894Speter if (c == '\n') 113711894Speter break; 113811894Speter if (c == SDELIM && delimstuffed) { 113911894Speter if (!--chars_read) 114011894Speter break; 114111894Speter cacheunget_(infile, c) 114211894Speter if (c != SDELIM) { 114311894Speter cacheget_(c) 114411894Speter break; 114511894Speter } 114611894Speter } 114711894Speter cs += kdelim_found; 114811894Speter kdelim_found |= c==KDELIM; 114911894Speter } 115011894Speter cacheget_(c) 115111894Speter done_backing_up:; 115211894Speter 115311894Speter /* Copy characters before `$Log' into LEADER. */ 115411894Speter bufalloc(&leader, cs); 115511894Speter cp = leader.string; 115611894Speter for (cw = 0; cw < cs; cw++) { 115711894Speter leader.string[cw] = c; 115811894Speter if (c == SDELIM && delimstuffed) 115911894Speter cacheget_(c) 116011894Speter cacheget_(c) 116111894Speter } 116211894Speter 116311894Speter /* Convert traditional C or Pascal leader to ` *'. */ 116411894Speter for (cw = 0; cw < cs; cw++) 116511894Speter if (ctab[(unsigned char) cp[cw]] != SPACE) 116611894Speter break; 116711894Speter if ( 116811894Speter cw+1 < cs 116911894Speter && cp[cw+1] == '*' 117011894Speter && (cp[cw] == '/' || cp[cw] == '(') 117111894Speter ) { 117211894Speter size_t i = cw+1; 117311894Speter for (;;) 117411894Speter if (++i == cs) { 117511894Speter warn( 117611894Speter "`%c* $Log' is obsolescent; use ` * $Log'.", 117711894Speter cp[cw] 117811894Speter ); 117911894Speter leader.string[cw] = ' '; 118011894Speter break; 118111894Speter } else if (ctab[(unsigned char) cp[i]] != SPACE) 118211894Speter break; 118311894Speter } 118411894Speter 118511894Speter /* Skip `$Log ... $' string. */ 118611894Speter do { 118711894Speter cacheget_(c) 118811894Speter } while (c != KDELIM); 118911894Speter uncache(infile); 119011894Speter } 11919Sjkh afputc('\n', out); 11929Sjkh awrite(cp, cs, out); 119311894Speter sp1 = date2str(date, datebuf); 119411894Speter if (VERSION(5) <= RCSv) { 119511894Speter aprintf(out, "Revision %s %s %s", 119611894Speter delta->num, sp1, delta->author 119711894Speter ); 119811894Speter } else { 119911894Speter /* oddity: 2 spaces between date and time, not 1 as usual */ 120011894Speter sp1 = strchr(sp1, ' '); 120111894Speter aprintf(out, "Revision %s %.*s %s %s", 120211894Speter delta->num, (int)(sp1-datebuf), datebuf, sp1, 120311894Speter delta->author 120411894Speter ); 120511894Speter } 12069Sjkh /* Do not include state: it may change and is not updated. */ 120711894Speter cw = cs; 12089Sjkh if (VERSION(5) <= RCSv) 12099Sjkh for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw) 121011894Speter continue; 12119Sjkh for (;;) { 12129Sjkh afputc('\n', out); 12139Sjkh awrite(cp, cw, out); 12149Sjkh if (!ls) 12159Sjkh break; 12169Sjkh --ls; 12179Sjkh c = *sp++; 12189Sjkh if (c != '\n') { 12199Sjkh awrite(cp+cw, cs-cw, out); 12209Sjkh do { 12219Sjkh afputc(c,out); 12229Sjkh if (!ls) 12239Sjkh break; 12249Sjkh --ls; 12259Sjkh c = *sp++; 12269Sjkh } while (c != '\n'); 12279Sjkh } 12289Sjkh } 122911894Speter bufautoend(&leader); 12309Sjkh } 12319Sjkh} 12329Sjkh 12339Sjkh#if has_readlink 123411894Speter static int resolve_symlink P((struct buf*)); 12359Sjkh static int 12369Sjkhresolve_symlink(L) 12379Sjkh struct buf *L; 12389Sjkh/* 12399Sjkh * If L is a symbolic link, resolve it to the name that it points to. 12409Sjkh * If unsuccessful, set errno and yield -1. 12419Sjkh * If it points to an existing file, yield 1. 12429Sjkh * Otherwise, set errno=ENOENT and yield 0. 12439Sjkh */ 12449Sjkh{ 12459Sjkh char *b, a[SIZEABLE_PATH]; 12469Sjkh int e; 12479Sjkh size_t s; 12489Sjkh ssize_t r; 12499Sjkh struct buf bigbuf; 125011894Speter int linkcount = MAXSYMLINKS; 12519Sjkh 12529Sjkh b = a; 12539Sjkh s = sizeof(a); 12549Sjkh bufautobegin(&bigbuf); 12559Sjkh while ((r = readlink(L->string,b,s)) != -1) 12569Sjkh if (r == s) { 12579Sjkh bufalloc(&bigbuf, s<<1); 12589Sjkh b = bigbuf.string; 12599Sjkh s = bigbuf.size; 126011894Speter } else if (!linkcount--) { 126111894Speter# ifndef ELOOP 126211894Speter /* 126311894Speter * Some pedantic Posix 1003.1-1990 hosts have readlink 126411894Speter * but not ELOOP. Approximate ELOOP with EMLINK. 126511894Speter */ 126611894Speter# define ELOOP EMLINK 126711894Speter# endif 12689Sjkh errno = ELOOP; 12699Sjkh return -1; 12709Sjkh } else { 12719Sjkh /* Splice symbolic link into L. */ 12729Sjkh b[r] = '\0'; 127311894Speter L->string[ 127411894Speter ROOTPATH(b) ? 0 : basefilename(L->string) - L->string 127511894Speter ] = '\0'; 12769Sjkh bufscat(L, b); 12779Sjkh } 12789Sjkh e = errno; 12799Sjkh bufautoend(&bigbuf); 12809Sjkh errno = e; 12819Sjkh switch (e) { 128211894Speter case readlink_isreg_errno: return 1; 12839Sjkh case ENOENT: return 0; 12849Sjkh default: return -1; 12859Sjkh } 12869Sjkh} 12879Sjkh#endif 12889Sjkh 12899Sjkh RILE * 12909Sjkhrcswriteopen(RCSbuf, status, mustread) 12919Sjkh struct buf *RCSbuf; 12929Sjkh struct stat *status; 12939Sjkh int mustread; 12949Sjkh/* 129511894Speter * Create the lock file corresponding to RCSBUF. 129611894Speter * Then try to open RCSBUF for reading and yield its RILE* descriptor. 12979Sjkh * Put its status into *STATUS too. 12989Sjkh * MUSTREAD is true if the file must already exist, too. 12999Sjkh * If all goes well, discard any previously acquired locks, 130011894Speter * and set fdlock to the file descriptor of the RCS lockfile. 13019Sjkh */ 13029Sjkh{ 13039Sjkh register char *tp; 130411894Speter register char const *sp, *RCSpath, *x; 13059Sjkh RILE *f; 13069Sjkh size_t l; 130711894Speter int e, exists, fdesc, fdescSafer, r, waslocked; 13089Sjkh struct buf *dirt; 13099Sjkh struct stat statbuf; 13109Sjkh 131111894Speter waslocked = 0 <= fdlock; 13129Sjkh exists = 13139Sjkh# if has_readlink 13149Sjkh resolve_symlink(RCSbuf); 13159Sjkh# else 13169Sjkh stat(RCSbuf->string, &statbuf) == 0 ? 1 13179Sjkh : errno==ENOENT ? 0 : -1; 13189Sjkh# endif 131911894Speter if (exists < (mustread|waslocked)) 13209Sjkh /* 13219Sjkh * There's an unusual problem with the RCS file; 13229Sjkh * or the RCS file doesn't exist, 13239Sjkh * and we must read or we already have a lock elsewhere. 13249Sjkh */ 13259Sjkh return 0; 13269Sjkh 132711894Speter RCSpath = RCSbuf->string; 132811894Speter sp = basefilename(RCSpath); 132911894Speter l = sp - RCSpath; 133011894Speter dirt = &dirtpname[waslocked]; 133111894Speter bufscpy(dirt, RCSpath); 13329Sjkh tp = dirt->string + l; 133311894Speter x = rcssuffix(RCSpath); 13349Sjkh# if has_readlink 13359Sjkh if (!x) { 133611894Speter error("symbolic link to non RCS file `%s'", RCSpath); 13379Sjkh errno = EINVAL; 13389Sjkh return 0; 13399Sjkh } 13409Sjkh# endif 13419Sjkh if (*sp == *x) { 134211894Speter error("RCS pathname `%s' incompatible with suffix `%s'", sp, x); 13439Sjkh errno = EINVAL; 13449Sjkh return 0; 13459Sjkh } 134611894Speter /* Create a lock filename that is a function of the RCS filename. */ 13479Sjkh if (*x) { 13489Sjkh /* 13499Sjkh * The suffix is nonempty. 13509Sjkh * The lock filename is the first char of of the suffix, 13519Sjkh * followed by the RCS filename with last char removed. E.g.: 13529Sjkh * foo,v RCS filename with suffix ,v 13539Sjkh * ,foo, lock filename 13549Sjkh */ 13559Sjkh *tp++ = *x; 13569Sjkh while (*sp) 13579Sjkh *tp++ = *sp++; 13589Sjkh *--tp = 0; 13599Sjkh } else { 13609Sjkh /* 13619Sjkh * The suffix is empty. 13629Sjkh * The lock filename is the RCS filename 13639Sjkh * with last char replaced by '_'. 13649Sjkh */ 13659Sjkh while ((*tp++ = *sp++)) 136611894Speter continue; 13679Sjkh tp -= 2; 13689Sjkh if (*tp == '_') { 136911894Speter error("RCS pathname `%s' ends with `%c'", RCSpath, *tp); 13709Sjkh errno = EINVAL; 13719Sjkh return 0; 13729Sjkh } 13739Sjkh *tp = '_'; 13749Sjkh } 13759Sjkh 137611894Speter sp = dirt->string; 13779Sjkh 13789Sjkh f = 0; 13799Sjkh 13809Sjkh /* 13819Sjkh * good news: 138211894Speter * open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY) 138311894Speter * is atomic according to Posix 1003.1-1990. 13849Sjkh * bad news: 13859Sjkh * NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990. 13869Sjkh * good news: 138711894Speter * (O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity 138811894Speter * even with NFS. 13899Sjkh * bad news: 139011894Speter * If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't 139111894Speter * guarantee atomicity. 13929Sjkh * good news: 13939Sjkh * Root-over-the-wire NFS access is rare for security reasons. 13949Sjkh * This bug has never been reported in practice with RCS. 13959Sjkh * So we don't worry about this bug. 13969Sjkh * 13979Sjkh * An even rarer NFS bug can occur when clients retry requests. 139811894Speter * This can happen in the usual case of NFS over UDP. 139911894Speter * Suppose client A releases a lock by renaming ",f," to "f,v" at 140011894Speter * about the same time that client B obtains a lock by creating ",f,", 14019Sjkh * and suppose A's first rename request is delayed, so A reissues it. 14029Sjkh * The sequence of events might be: 14039Sjkh * A sends rename(",f,", "f,v") 14049Sjkh * B sends create(",f,") 14059Sjkh * A sends retry of rename(",f,", "f,v") 14069Sjkh * server receives, does, and acknowledges A's first rename() 14079Sjkh * A receives acknowledgment, and its RCS program exits 14089Sjkh * server receives, does, and acknowledges B's create() 14099Sjkh * server receives, does, and acknowledges A's retry of rename() 14109Sjkh * This not only wrongly deletes B's lock, it removes the RCS file! 14119Sjkh * Most NFS implementations have idempotency caches that usually prevent 14129Sjkh * this scenario, but such caches are finite and can be overrun. 141311894Speter * This problem afflicts not only RCS, which uses open() and rename() 141411894Speter * to get and release locks; it also afflicts the traditional 14159Sjkh * Unix method of using link() and unlink() to get and release locks, 141611894Speter * and the less traditional method of using mkdir() and rmdir(). 141711894Speter * There is no easy workaround. 14189Sjkh * Any new method based on lockf() seemingly would be incompatible with 14199Sjkh * the old methods; besides, lockf() is notoriously buggy under NFS. 14209Sjkh * Since this problem afflicts scads of Unix programs, but is so rare 14219Sjkh * that nobody seems to be worried about it, we won't worry either. 14229Sjkh */ 14239Sjkh# if !open_can_creat 142411894Speter# define create(f) creat(f, OPEN_CREAT_READONLY) 14259Sjkh# else 142611894Speter# define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY) 14279Sjkh# endif 14289Sjkh 14299Sjkh catchints(); 14309Sjkh ignoreints(); 14319Sjkh 14329Sjkh /* 14339Sjkh * Create a lock file for an RCS file. This should be atomic, i.e. 14349Sjkh * if two processes try it simultaneously, at most one should succeed. 14359Sjkh */ 14369Sjkh seteid(); 14379Sjkh fdesc = create(sp); 143811894Speter fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr. */ 14399Sjkh e = errno; 14409Sjkh setrid(); 14419Sjkh 144211894Speter if (0 <= fdesc) 144311894Speter dirtpmaker[0] = effective; 144411894Speter 144511894Speter if (fdescSafer < 0) { 144611894Speter if (e == EACCES && stat(sp,&statbuf) == 0) 14479Sjkh /* The RCS file is busy. */ 14489Sjkh e = EEXIST; 14499Sjkh } else { 14509Sjkh e = ENOENT; 14519Sjkh if (exists) { 145211894Speter f = Iopen(RCSpath, FOPEN_RB, status); 14539Sjkh e = errno; 145411894Speter if (f && waslocked) { 14559Sjkh /* Discard the previous lock in favor of this one. */ 145611894Speter ORCSclose(); 14579Sjkh seteid(); 145811894Speter r = un_link(lockname); 145911894Speter e = errno; 14609Sjkh setrid(); 14619Sjkh if (r != 0) 146211894Speter enfaterror(e, lockname); 146311894Speter bufscpy(&dirtpname[lockdirtp_index], sp); 14649Sjkh } 14659Sjkh } 146611894Speter fdlock = fdescSafer; 14679Sjkh } 14689Sjkh 14699Sjkh restoreints(); 14709Sjkh 14719Sjkh errno = e; 14729Sjkh return f; 14739Sjkh} 14749Sjkh 14759Sjkh void 14769Sjkhkeepdirtemp(name) 14779Sjkh char const *name; 14789Sjkh/* Do not unlink name, either because it's not there any more, 14799Sjkh * or because it has already been unlinked. 14809Sjkh */ 14819Sjkh{ 14829Sjkh register int i; 14839Sjkh for (i=DIRTEMPNAMES; 0<=--i; ) 148411894Speter if (dirtpname[i].string == name) { 148511894Speter dirtpmaker[i] = notmade; 14869Sjkh return; 14879Sjkh } 14889Sjkh faterror("keepdirtemp"); 14899Sjkh} 14909Sjkh 14919Sjkh char const * 149211894Spetermakedirtemp(isworkfile) 149311894Speter int isworkfile; 14949Sjkh/* 149511894Speter * Create a unique pathname and store it into dirtpname. 149611894Speter * Because of storage in tpnames, dirtempunlink() can unlink the file later. 149711894Speter * Return a pointer to the pathname created. 149811894Speter * If ISWORKFILE is 1, put it into the working file's directory; 149911894Speter * if 0, put the unique file in RCSfile's directory. 15009Sjkh */ 15019Sjkh{ 15029Sjkh register char *tp, *np; 15039Sjkh register size_t dl; 15049Sjkh register struct buf *bn; 150511894Speter register char const *name = isworkfile ? workname : RCSname; 15069Sjkh 150711894Speter dl = basefilename(name) - name; 150811894Speter bn = &dirtpname[newRCSdirtp_index + isworkfile]; 15099Sjkh bufalloc(bn, 15109Sjkh# if has_mktemp 15119Sjkh dl + 9 15129Sjkh# else 15139Sjkh strlen(name) + 3 15149Sjkh# endif 15159Sjkh ); 15169Sjkh bufscpy(bn, name); 15179Sjkh np = tp = bn->string; 15189Sjkh tp += dl; 15199Sjkh *tp++ = '_'; 152011894Speter *tp++ = '0'+isworkfile; 15219Sjkh catchints(); 15229Sjkh# if has_mktemp 15239Sjkh VOID strcpy(tp, "XXXXXX"); 15249Sjkh if (!mktemp(np) || !*np) 152511894Speter faterror("can't make temporary pathname `%.*s_%cXXXXXX'", 152611894Speter (int)dl, name, '0'+isworkfile 15279Sjkh ); 15289Sjkh# else 15299Sjkh /* 15309Sjkh * Posix 1003.1-1990 has no reliable way 15319Sjkh * to create a unique file in a named directory. 153211894Speter * We fudge here. If the filename is abcde, 15339Sjkh * the temp filename is _Ncde where N is a digit. 15349Sjkh */ 15359Sjkh name += dl; 15369Sjkh if (*name) name++; 15379Sjkh if (*name) name++; 15389Sjkh VOID strcpy(tp, name); 15399Sjkh# endif 154011894Speter dirtpmaker[newRCSdirtp_index + isworkfile] = real; 15419Sjkh return np; 15429Sjkh} 15439Sjkh 15449Sjkh void 15459Sjkhdirtempunlink() 15469Sjkh/* Clean up makedirtemp() files. May be invoked by signal handler. */ 15479Sjkh{ 15489Sjkh register int i; 15499Sjkh enum maker m; 15509Sjkh 15519Sjkh for (i = DIRTEMPNAMES; 0 <= --i; ) 155211894Speter if ((m = dirtpmaker[i]) != notmade) { 15539Sjkh if (m == effective) 15549Sjkh seteid(); 155511894Speter VOID un_link(dirtpname[i].string); 15569Sjkh if (m == effective) 15579Sjkh setrid(); 155811894Speter dirtpmaker[i] = notmade; 15599Sjkh } 15609Sjkh} 15619Sjkh 15629Sjkh 15639Sjkh int 15649Sjkh#if has_prototypes 156511894Speterchnamemod( 156611894Speter FILE **fromp, char const *from, char const *to, 156711894Speter int set_mode, mode_t mode, time_t mtime 156811894Speter) 15699Sjkh /* The `#if has_prototypes' is needed because mode_t might promote to int. */ 15709Sjkh#else 157111894Speter chnamemod(fromp, from, to, set_mode, mode, mtime) 157211894Speter FILE **fromp; char const *from,*to; 157311894Speter int set_mode; mode_t mode; time_t mtime; 15749Sjkh#endif 15759Sjkh/* 157611894Speter * Rename a file (with stream pointer *FROMP) from FROM to TO. 15779Sjkh * FROM already exists. 157811894Speter * If 0 < SET_MODE, change the mode to MODE, before renaming if possible. 157911894Speter * If MTIME is not -1, change its mtime to MTIME before renaming. 158011894Speter * Close and clear *FROMP before renaming it. 15819Sjkh * Unlink TO if it already exists. 15829Sjkh * Return -1 on error (setting errno), 0 otherwise. 15839Sjkh */ 15849Sjkh{ 158511894Speter mode_t mode_while_renaming = mode; 158611894Speter int fchmod_set_mode = 0; 158711894Speter 158811894Speter# if bad_a_rename || bad_NFS_rename 158911894Speter struct stat st; 159011894Speter if (bad_NFS_rename || (bad_a_rename && set_mode <= 0)) { 159111894Speter if (fstat(fileno(*fromp), &st) != 0) 159211894Speter return -1; 159311894Speter if (bad_a_rename && set_mode <= 0) 159411894Speter mode = st.st_mode; 159511894Speter } 159611894Speter# endif 159711894Speter 15989Sjkh# if bad_a_rename 15999Sjkh /* 160011894Speter * There's a short window of inconsistency 160111894Speter * during which the lock file is writable. 160211894Speter */ 160311894Speter mode_while_renaming = mode|S_IWUSR; 160411894Speter if (mode != mode_while_renaming) 160511894Speter set_mode = 1; 16069Sjkh# endif 160711894Speter 16089Sjkh# if has_fchmod 160911894Speter if (0<set_mode && fchmod(fileno(*fromp),mode_while_renaming) == 0) 161011894Speter fchmod_set_mode = set_mode; 16119Sjkh# endif 161211894Speter /* If bad_chmod_close, we must close before chmod. */ 161311894Speter Ozclose(fromp); 161411894Speter if (fchmod_set_mode<set_mode && chmod(from, mode_while_renaming) != 0) 161511894Speter return -1; 161611894Speter 161711894Speter if (setmtime(from, mtime) != 0) 16189Sjkh return -1; 16199Sjkh 16209Sjkh# if !has_rename || bad_b_rename 16219Sjkh /* 162211894Speter * There's a short window of inconsistency 162311894Speter * during which TO does not exist. 162411894Speter */ 162511894Speter if (un_link(to) != 0 && errno != ENOENT) 162611894Speter return -1; 16279Sjkh# endif 16289Sjkh 162911894Speter# if has_rename 163011894Speter if (rename(from,to) != 0 && !(has_NFS && errno==ENOENT)) 163111894Speter return -1; 163211894Speter# else 163311894Speter if (do_link(from,to) != 0 || un_link(from) != 0) 163411894Speter return -1; 163511894Speter# endif 16369Sjkh 163711894Speter# if bad_NFS_rename 163811894Speter { 163911894Speter /* 164011894Speter * Check whether the rename falsely reported success. 164111894Speter * A race condition can occur between the rename and the stat. 164211894Speter */ 164311894Speter struct stat tostat; 164411894Speter if (stat(to, &tostat) != 0) 164511894Speter return -1; 164611894Speter if (! same_file(st, tostat, 0)) { 164711894Speter errno = EIO; 164811894Speter return -1; 164911894Speter } 165011894Speter } 165111894Speter# endif 165211894Speter 165311894Speter# if bad_a_rename 165411894Speter if (0 < set_mode && chmod(to, mode) != 0) 165511894Speter return -1; 165611894Speter# endif 165711894Speter 165811894Speter return 0; 16599Sjkh} 16609Sjkh 166111894Speter int 166211894Spetersetmtime(file, mtime) 166311894Speter char const *file; 166411894Speter time_t mtime; 166511894Speter/* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1. */ 166611894Speter{ 166711894Speter static struct utimbuf amtime; /* static so unused fields are zero */ 166811894Speter if (mtime == -1) 166911894Speter return 0; 167011894Speter amtime.actime = now(); 167111894Speter amtime.modtime = mtime; 167211894Speter return utime(file, &amtime); 167311894Speter} 16749Sjkh 16759Sjkh 167611894Speter 16779Sjkh int 16789Sjkhfindlock(delete, target) 16799Sjkh int delete; 16809Sjkh struct hshentry **target; 16819Sjkh/* 16829Sjkh * Find the first lock held by caller and return a pointer 16839Sjkh * to the locked delta; also removes the lock if DELETE. 16849Sjkh * If one lock, put it into *TARGET. 16859Sjkh * Return 0 for no locks, 1 for one, 2 for two or more. 16869Sjkh */ 16879Sjkh{ 168811894Speter register struct rcslock *next, **trail, **found; 16899Sjkh 16909Sjkh found = 0; 16919Sjkh for (trail = &Locks; (next = *trail); trail = &next->nextlock) 16929Sjkh if (strcmp(getcaller(), next->login) == 0) { 16939Sjkh if (found) { 169411894Speter rcserror("multiple revisions locked by %s; please specify one", getcaller()); 16959Sjkh return 2; 16969Sjkh } 16979Sjkh found = trail; 16989Sjkh } 16999Sjkh if (!found) 17009Sjkh return 0; 17019Sjkh next = *found; 17029Sjkh *target = next->delta; 17039Sjkh if (delete) { 170411894Speter next->delta->lockedby = 0; 17059Sjkh *found = next->nextlock; 17069Sjkh } 17079Sjkh return 1; 17089Sjkh} 17099Sjkh 17109Sjkh int 171111894Speteraddlock(delta, verbose) 17129Sjkh struct hshentry * delta; 171311894Speter int verbose; 17149Sjkh/* 17159Sjkh * Add a lock held by caller to DELTA and yield 1 if successful. 171611894Speter * Print an error message if verbose and yield -1 if no lock is added because 17179Sjkh * DELTA is locked by somebody other than caller. 17189Sjkh * Return 0 if the caller already holds the lock. 17199Sjkh */ 17209Sjkh{ 172111894Speter register struct rcslock *next; 17229Sjkh 17239Sjkh for (next = Locks; next; next = next->nextlock) 17249Sjkh if (cmpnum(delta->num, next->delta->num) == 0) 17259Sjkh if (strcmp(getcaller(), next->login) == 0) 17269Sjkh return 0; 17279Sjkh else { 172811894Speter if (verbose) 172911894Speter rcserror("Revision %s is already locked by %s.", 173011894Speter delta->num, next->login 173111894Speter ); 17329Sjkh return -1; 17339Sjkh } 173411894Speter next = ftalloc(struct rcslock); 17359Sjkh delta->lockedby = next->login = getcaller(); 17369Sjkh next->delta = delta; 17379Sjkh next->nextlock = Locks; 17389Sjkh Locks = next; 17399Sjkh return 1; 17409Sjkh} 17419Sjkh 17429Sjkh 17439Sjkh int 17449Sjkhaddsymbol(num, name, rebind) 17459Sjkh char const *num, *name; 17469Sjkh int rebind; 17479Sjkh/* 17489Sjkh * Associate with revision NUM the new symbolic NAME. 17499Sjkh * If NAME already exists and REBIND is set, associate NAME with NUM; 17509Sjkh * otherwise, print an error message and return false; 175111894Speter * Return -1 if unsuccessful, 0 if no change, 1 if change. 17529Sjkh */ 17539Sjkh{ 17549Sjkh register struct assoc *next; 17559Sjkh 17569Sjkh for (next = Symbols; next; next = next->nextassoc) 17579Sjkh if (strcmp(name, next->symbol) == 0) 175811894Speter if (strcmp(next->num,num) == 0) 175911894Speter return 0; 176011894Speter else if (rebind) { 17619Sjkh next->num = num; 176211894Speter return 1; 17639Sjkh } else { 176411894Speter rcserror("symbolic name %s already bound to %s", 17659Sjkh name, next->num 17669Sjkh ); 176711894Speter return -1; 17689Sjkh } 17699Sjkh next = ftalloc(struct assoc); 17709Sjkh next->symbol = name; 17719Sjkh next->num = num; 17729Sjkh next->nextassoc = Symbols; 17739Sjkh Symbols = next; 177411894Speter return 1; 17759Sjkh} 17769Sjkh 17779Sjkh 17789Sjkh 17799Sjkh char const * 17809Sjkhgetcaller() 17819Sjkh/* Get the caller's login name. */ 17829Sjkh{ 17839Sjkh# if has_setuid 17849Sjkh return getusername(euid()!=ruid()); 17859Sjkh# else 17869Sjkh return getusername(false); 17879Sjkh# endif 17889Sjkh} 17899Sjkh 17909Sjkh 17919Sjkh int 17929Sjkhcheckaccesslist() 17939Sjkh/* 17949Sjkh * Return true if caller is the superuser, the owner of the 17959Sjkh * file, the access list is empty, or caller is on the access list. 17969Sjkh * Otherwise, print an error message and return false. 17979Sjkh */ 17989Sjkh{ 17999Sjkh register struct access const *next; 18009Sjkh 18019Sjkh if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0) 18029Sjkh return true; 18039Sjkh 18049Sjkh next = AccessList; 18059Sjkh do { 18069Sjkh if (strcmp(getcaller(), next->login) == 0) 18079Sjkh return true; 18089Sjkh } while ((next = next->nextaccess)); 18099Sjkh 181011894Speter rcserror("user %s not on the access list", getcaller()); 18119Sjkh return false; 18129Sjkh} 18139Sjkh 18149Sjkh 18159Sjkh int 18169Sjkhdorewrite(lockflag, changed) 18179Sjkh int lockflag, changed; 18189Sjkh/* 18199Sjkh * Do nothing if LOCKFLAG is zero. 18209Sjkh * Prepare to rewrite an RCS file if CHANGED is positive. 18219Sjkh * Stop rewriting if CHANGED is zero, because there won't be any changes. 18229Sjkh * Fail if CHANGED is negative. 182311894Speter * Return 0 on success, -1 on failure. 18249Sjkh */ 18259Sjkh{ 182611894Speter int r = 0, e; 18279Sjkh 18289Sjkh if (lockflag) 18299Sjkh if (changed) { 18309Sjkh if (changed < 0) 183111894Speter return -1; 183211894Speter putadmin(); 18339Sjkh puttree(Head, frewrite); 18349Sjkh aprintf(frewrite, "\n\n%s%c", Kdesc, nextc); 18359Sjkh foutptr = frewrite; 18369Sjkh } else { 183711894Speter# if bad_creat0 183811894Speter int nr = !!frewrite, ne = 0; 183911894Speter# endif 184011894Speter ORCSclose(); 18419Sjkh seteid(); 18429Sjkh ignoreints(); 184311894Speter# if bad_creat0 184411894Speter if (nr) { 184511894Speter nr = un_link(newRCSname); 184611894Speter ne = errno; 184711894Speter keepdirtemp(newRCSname); 184811894Speter } 184911894Speter# endif 185011894Speter r = un_link(lockname); 18519Sjkh e = errno; 185211894Speter keepdirtemp(lockname); 18539Sjkh restoreints(); 18549Sjkh setrid(); 185511894Speter if (r != 0) 185611894Speter enerror(e, lockname); 185711894Speter# if bad_creat0 185811894Speter if (nr != 0) { 185911894Speter enerror(ne, newRCSname); 186011894Speter r = -1; 186111894Speter } 186211894Speter# endif 18639Sjkh } 186411894Speter return r; 18659Sjkh} 18669Sjkh 18679Sjkh int 186811894Speterdonerewrite(changed, newRCStime) 18699Sjkh int changed; 187011894Speter time_t newRCStime; 18719Sjkh/* 18729Sjkh * Finish rewriting an RCS file if CHANGED is nonzero. 187311894Speter * Set its mode if CHANGED is positive. 187411894Speter * Set its modification time to NEWRCSTIME unless it is -1. 187511894Speter * Return 0 on success, -1 on failure. 18769Sjkh */ 18779Sjkh{ 187811894Speter int r = 0, e = 0; 187911894Speter# if bad_creat0 188011894Speter int lr, le; 188111894Speter# endif 18829Sjkh 18839Sjkh if (changed && !nerror) { 18849Sjkh if (finptr) { 18859Sjkh fastcopy(finptr, frewrite); 18869Sjkh Izclose(&finptr); 18879Sjkh } 18889Sjkh if (1 < RCSstat.st_nlink) 188911894Speter rcswarn("breaking hard link"); 189011894Speter aflush(frewrite); 18919Sjkh seteid(); 18929Sjkh ignoreints(); 189311894Speter r = chnamemod( 189411894Speter &frewrite, newRCSname, RCSname, changed, 189511894Speter RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH), 189611894Speter newRCStime 18979Sjkh ); 18989Sjkh e = errno; 189911894Speter keepdirtemp(newRCSname); 190011894Speter# if bad_creat0 190111894Speter lr = un_link(lockname); 190211894Speter le = errno; 190311894Speter keepdirtemp(lockname); 190411894Speter# endif 19059Sjkh restoreints(); 19069Sjkh setrid(); 19079Sjkh if (r != 0) { 190811894Speter enerror(e, RCSname); 190911894Speter error("saved in %s", newRCSname); 19109Sjkh } 191111894Speter# if bad_creat0 191211894Speter if (lr != 0) { 191311894Speter enerror(le, lockname); 191411894Speter r = -1; 191511894Speter } 191611894Speter# endif 19179Sjkh } 191811894Speter return r; 19199Sjkh} 19209Sjkh 19219Sjkh void 192211894SpeterORCSclose() 19239Sjkh{ 192411894Speter if (0 <= fdlock) { 192511894Speter if (close(fdlock) != 0) 192611894Speter efaterror(lockname); 192711894Speter fdlock = -1; 192811894Speter } 192911894Speter Ozclose(&frewrite); 19309Sjkh} 193111894Speter 193211894Speter void 193311894SpeterORCSerror() 193411894Speter/* 193511894Speter* Like ORCSclose, except we are cleaning up after an interrupt or fatal error. 193611894Speter* Do not report errors, since this may loop. This is needed only because 193711894Speter* some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and 193811894Speter* some nearly-Posix hosts (e.g. NFS) work better if the files are closed first. 193911894Speter* This isn't a completely reliable away to work around brain-damaged hosts, 194011894Speter* because of the gap between actual file opening and setting frewrite etc., 194111894Speter* but it's better than nothing. 194211894Speter*/ 194311894Speter{ 194411894Speter if (0 <= fdlock) 194511894Speter VOID close(fdlock); 194611894Speter if (frewrite) 194711894Speter /* Avoid fclose, since stdio may not be reentrant. */ 194811894Speter VOID close(fileno(frewrite)); 194911894Speter} 1950