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 * Revision 5.19 1995/06/16 06:19:24 eggert 3911894Speter * Update FSF address. 408858Srgrimes * 4111894Speter * Revision 5.18 1995/06/01 16:23:43 eggert 4211894Speter * (dirtpname): No longer external. 4311894Speter * (do_link): Simplify logic. 4411894Speter * (finisheditline, finishedit): Replace Iseek/Itell with what they stand for. 4511894Speter * (fopen_update_truncate): Replace `#if' with `if'. 4611894Speter * (keyreplace, makedirtemp): dirlen(x) -> basefilename(x)-x. 471494Srgrimes * 4811894Speter * (edit_string): Fix bug: if !large_memory, a bogus trailing `@' was output 4911894Speter * at the end of incomplete lines. 5011894Speter * 5111894Speter * (keyreplace): Do not assume that seeking backwards 5211894Speter * at the start of a file will fail; on some systems it succeeds. 5311894Speter * Convert C- and Pascal-style comment starts to ` *' in comment leader. 5411894Speter * 5511894Speter * (rcswriteopen): Use fdSafer to get safer file descriptor. 5611894Speter * Open RCS file with FOPEN_RB. 5711894Speter * 5811894Speter * (chnamemod): Work around bad_NFS_rename bug; don't ignore un_link result. 5911894Speter * Fall back on chmod if fchmod fails, since it might be ENOSYS. 6011894Speter * 6111894Speter * (aflush): Move to rcslex.c. 6211894Speter * 6311894Speter * Revision 5.17 1994/03/20 04:52:58 eggert 6411894Speter * Normally calculate the $Log prefix from context, not from RCS file. 6511894Speter * Move setmtime here from rcsutil.c. Add ORCSerror. Remove lint. 6611894Speter * 6711894Speter * Revision 5.16 1993/11/03 17:42:27 eggert 6811894Speter * Add -z. Add Name keyword. If bad_unlink, ignore errno when unlink fails. 6911894Speter * Escape white space, $, and \ in keyword string file names. 7011894Speter * Don't output 2 spaces between date and time after Log. 7111894Speter * 7211894Speter * Revision 5.15 1992/07/28 16:12:44 eggert 7311894Speter * Some hosts have readlink but not ELOOP. Avoid `unsigned'. 7411894Speter * Preserve dates more systematically. Statement macro names now end in _. 7511894Speter * 7611894Speter * Revision 5.14 1992/02/17 23:02:24 eggert 7711894Speter * Add -T support. 7811894Speter * 7911894Speter * Revision 5.13 1992/01/24 18:44:19 eggert 8011894Speter * Add support for bad_chmod_close, bad_creat0. 8111894Speter * 8211894Speter * Revision 5.12 1992/01/06 02:42:34 eggert 8311894Speter * Add setmode parameter to chnamemod. addsymbol now reports changes. 8411894Speter * while (E) ; -> while (E) continue; 8511894Speter * 869Sjkh * Revision 5.11 1991/11/03 01:11:44 eggert 879Sjkh * Move the warning about link breaking to where they're actually being broken. 889Sjkh * 899Sjkh * Revision 5.10 1991/10/07 17:32:46 eggert 909Sjkh * Support piece tables even if !has_mmap. Fix rare NFS bugs. 919Sjkh * 929Sjkh * Revision 5.9 1991/09/17 19:07:40 eggert 939Sjkh * SGI readlink() yields ENXIO, not EINVAL, for nonlinks. 949Sjkh * 959Sjkh * Revision 5.8 1991/08/19 03:13:55 eggert 969Sjkh * Add piece tables, NFS bug workarounds. Catch odd filenames. Tune. 979Sjkh * 989Sjkh * Revision 5.7 1991/04/21 11:58:21 eggert 999Sjkh * Fix errno bugs. Add -x, RCSINIT, MS-DOS support. 1009Sjkh * 1019Sjkh * Revision 5.6 1991/02/25 07:12:40 eggert 1029Sjkh * Fix setuid bug. Support new link behavior. Work around broken "w+" fopen. 1039Sjkh * 1049Sjkh * Revision 5.5 1990/12/30 05:07:35 eggert 1059Sjkh * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL). 1069Sjkh * 1079Sjkh * Revision 5.4 1990/11/01 05:03:40 eggert 1089Sjkh * Permit arbitrary data in comment leaders. 1099Sjkh * 1109Sjkh * Revision 5.3 1990/09/11 02:41:13 eggert 1119Sjkh * Tune expandline(). 1129Sjkh * 1139Sjkh * Revision 5.2 1990/09/04 08:02:21 eggert 1149Sjkh * Count RCS lines better. Improve incomplete line handling. 1159Sjkh * 1169Sjkh * Revision 5.1 1990/08/29 07:13:56 eggert 1179Sjkh * Add -kkvl. 1189Sjkh * Fix bug when getting revisions to files ending in incomplete lines. 1199Sjkh * Fix bug in comment leader expansion. 1209Sjkh * 1219Sjkh * Revision 5.0 1990/08/22 08:12:47 eggert 1229Sjkh * Don't require final newline. 1239Sjkh * Don't append "checked in with -k by " to logs, 1249Sjkh * so that checking in a program with -k doesn't change it. 1259Sjkh * Don't generate trailing white space for empty comment leader. 1269Sjkh * Remove compile-time limits; use malloc instead. Add -k, -V. 1279Sjkh * Permit dates past 1999/12/31. Make lock and temp files faster and safer. 1289Sjkh * Ansify and Posixate. Check diff's output. 1299Sjkh * 1309Sjkh * Revision 4.8 89/05/01 15:12:35 narten 1319Sjkh * changed copyright header to reflect current distribution rules 1328858Srgrimes * 1339Sjkh * Revision 4.7 88/11/08 13:54:14 narten 1349Sjkh * misplaced semicolon caused infinite loop 1358858Srgrimes * 1369Sjkh * Revision 4.6 88/08/09 19:12:45 eggert 1379Sjkh * Shrink stdio code size; allow cc -R. 1388858Srgrimes * 1399Sjkh * Revision 4.5 87/12/18 11:38:46 narten 1409Sjkh * Changes from the 43. version. Don't know the significance of the 1419Sjkh * first change involving "rewind". Also, additional "lint" cleanup. 1429Sjkh * (Guy Harris) 1438858Srgrimes * 1449Sjkh * Revision 4.4 87/10/18 10:32:21 narten 1459Sjkh * Updating version numbers. Changes relative to version 1.1 actually 1469Sjkh * relative to 4.1 1478858Srgrimes * 1489Sjkh * Revision 1.4 87/09/24 13:59:29 narten 1498858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf 1509Sjkh * warnings) 1518858Srgrimes * 1529Sjkh * Revision 1.3 87/09/15 16:39:39 shepler 1539Sjkh * added an initializatin of the variables editline and linecorr 1549Sjkh * this will be done each time a file is processed. 1559Sjkh * (there was an obscure bug where if co was used to retrieve multiple files 1569Sjkh * it would dump) 1579Sjkh * fix attributed to Roy Morris @FileNet Corp ...!felix!roy 1588858Srgrimes * 1599Sjkh * Revision 1.2 87/03/27 14:22:17 jenkins 1609Sjkh * Port to suns 1618858Srgrimes * 1629Sjkh * Revision 4.1 83/05/12 13:10:30 wft 1639Sjkh * Added new markers Id and RCSfile; added locker to Header and Id. 1649Sjkh * Overhauled expandline completely() (problem with $01234567890123456789@). 1659Sjkh * Moved trymatch() and marker table to rcskeys.c. 1668858Srgrimes * 1679Sjkh * Revision 3.7 83/05/12 13:04:39 wft 1689Sjkh * Added retry to expandline to resume after failed match which ended in $. 1699Sjkh * Fixed truncation problem for $19chars followed by@@. 1709Sjkh * Log no longer expands full path of RCS file. 1718858Srgrimes * 1729Sjkh * Revision 3.6 83/05/11 16:06:30 wft 1739Sjkh * added retry to expandline to resume after failed match which ended in $. 1749Sjkh * Fixed truncation problem for $19chars followed by@@. 1758858Srgrimes * 1769Sjkh * Revision 3.5 82/12/04 13:20:56 wft 1779Sjkh * Added expansion of keyword Locker. 1789Sjkh * 1799Sjkh * Revision 3.4 82/12/03 12:26:54 wft 1809Sjkh * Added line number correction in case editing does not start at the 1819Sjkh * beginning of the file. 1829Sjkh * Changed keyword expansion to always print a space before closing KDELIM; 1839Sjkh * Expansion for Header shortened. 1849Sjkh * 1859Sjkh * Revision 3.3 82/11/14 14:49:30 wft 1869Sjkh * removed Suffix from keyword expansion. Replaced fclose with ffclose. 1879Sjkh * keyreplace() gets log message from delta, not from curlogmsg. 1889Sjkh * fixed expression overflow in while(c=putc(GETC.... 1899Sjkh * checked nil printing. 1909Sjkh * 1919Sjkh * Revision 3.2 82/10/18 21:13:39 wft 1929Sjkh * I added checks for write errors during the co process, and renamed 1939Sjkh * expandstring() to xpandstring(). 1949Sjkh * 1959Sjkh * Revision 3.1 82/10/13 15:52:55 wft 1969Sjkh * changed type of result of getc() from char to int. 1979Sjkh * made keyword expansion loop in expandline() portable to machines 1989Sjkh * without sign-extension. 1999Sjkh */ 2009Sjkh 2019Sjkh 2029Sjkh#include "rcsbase.h" 2039Sjkh 20450472SpeterlibId(editId, "$FreeBSD$") 2059Sjkh 20611894Speterstatic void editEndsPrematurely P((void)) exiting; 20711894Speterstatic void editLineNumberOverflow P((void)) exiting; 20811894Speterstatic void escape_string P((FILE*,char const*)); 20911894Speterstatic void keyreplace P((enum markers,struct hshentry const*,int,RILE*,FILE*,int)); 2109Sjkh 2119SjkhFILE *fcopy; /* result file descriptor */ 21211894Speterchar const *resultname; /* result pathname */ 2139Sjkhint locker_expansion; /* should the locker name be appended to Id val? */ 2149Sjkh#if !large_memory 2159Sjkh static RILE *fedit; /* edit file descriptor */ 21611894Speter static char const *editname; /* edit pathname */ 2179Sjkh#endif 21811894Speterstatic long editline; /* edit line counter; #lines before cursor */ 2199Sjkhstatic long linecorr; /* #adds - #deletes in each edit run. */ 2209Sjkh /*used to correct editline in case file is not rewound after */ 2219Sjkh /* applying one delta */ 2229Sjkh 22311894Speter/* indexes into dirtpname */ 22411894Speter#define lockdirtp_index 0 22511894Speter#define newRCSdirtp_index bad_creat0 22611894Speter#define newworkdirtp_index (newRCSdirtp_index+1) 22711894Speter#define DIRTEMPNAMES (newworkdirtp_index + 1) 22811894Speter 2299Sjkhenum maker {notmade, real, effective}; 23011894Speterstatic struct buf dirtpname[DIRTEMPNAMES]; /* unlink these when done */ 23111894Speterstatic enum maker volatile dirtpmaker[DIRTEMPNAMES]; /* if these are set */ 23211894Speter#define lockname (dirtpname[lockdirtp_index].string) 23311894Speter#define newRCSname (dirtpname[newRCSdirtp_index].string) 2349Sjkh 2359Sjkh 2369Sjkh#if has_NFS || bad_unlink 2379Sjkh int 2389Sjkhun_link(s) 2399Sjkh char const *s; 2409Sjkh/* 2419Sjkh * Remove S, even if it is unwritable. 2429Sjkh * Ignore unlink() ENOENT failures; NFS generates bogus ones. 2439Sjkh */ 2449Sjkh{ 2459Sjkh# if bad_unlink 2469Sjkh if (unlink(s) == 0) 2479Sjkh return 0; 24811894Speter else { 24911894Speter int e = errno; 25011894Speter /* 25111894Speter * Forge ahead even if errno == ENOENT; some completely 25211894Speter * brain-damaged hosts (e.g. PCTCP 2.2) yield ENOENT 25311894Speter * even for existing unwritable files. 25411894Speter */ 25511894Speter if (chmod(s, S_IWUSR) != 0) { 25611894Speter errno = e; 25711894Speter return -1; 25811894Speter } 2599Sjkh } 2609Sjkh# endif 2619Sjkh# if has_NFS 2629Sjkh return unlink(s)==0 || errno==ENOENT ? 0 : -1; 2639Sjkh# else 2649Sjkh return unlink(s); 2659Sjkh# endif 2669Sjkh} 2679Sjkh#endif 2689Sjkh 2699Sjkh#if !has_rename 2709Sjkh# if !has_NFS 2719Sjkh# define do_link(s,t) link(s,t) 2729Sjkh# else 27311894Speter static int do_link P((char const*,char const*)); 2749Sjkh static int 2759Sjkhdo_link(s, t) 2769Sjkh char const *s, *t; 2779Sjkh/* Link S to T, ignoring bogus EEXIST problems due to NFS failures. */ 2789Sjkh{ 27911894Speter int r = link(s, t); 2809Sjkh 28111894Speter if (r != 0 && errno == EEXIST) { 28211894Speter struct stat sb, tb; 28311894Speter if ( 28411894Speter stat(s, &sb) == 0 && 28511894Speter stat(t, &tb) == 0 && 28611894Speter same_file(sb, tb, 0) 28711894Speter ) 28811894Speter r = 0; 28911894Speter errno = EEXIST; 29011894Speter } 29111894Speter return r; 2929Sjkh} 2939Sjkh# endif 2949Sjkh#endif 2959Sjkh 2969Sjkh 29711894Speter static void 2989SjkheditEndsPrematurely() 2999Sjkh{ 3009Sjkh fatserror("edit script ends prematurely"); 3019Sjkh} 3029Sjkh 30311894Speter static void 3049SjkheditLineNumberOverflow() 3059Sjkh{ 3069Sjkh fatserror("edit script refers to line past end of file"); 3079Sjkh} 3089Sjkh 3099Sjkh 3109Sjkh#if large_memory 3119Sjkh 3129Sjkh#if has_memmove 3139Sjkh# define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type)) 3149Sjkh#else 31511894Speter static void movelines P((Iptr_type*,Iptr_type const*,long)); 3169Sjkh static void 3179Sjkhmovelines(s1, s2, n) 3189Sjkh register Iptr_type *s1; 3199Sjkh register Iptr_type const *s2; 32011894Speter register long n; 3219Sjkh{ 3229Sjkh if (s1 < s2) 3239Sjkh do { 3249Sjkh *s1++ = *s2++; 3259Sjkh } while (--n); 3269Sjkh else { 3279Sjkh s1 += n; 3289Sjkh s2 += n; 3299Sjkh do { 3309Sjkh *--s1 = *--s2; 3319Sjkh } while (--n); 3329Sjkh } 3339Sjkh} 3349Sjkh#endif 3359Sjkh 33611894Speterstatic void deletelines P((long,long)); 33711894Speterstatic void finisheditline P((RILE*,FILE*,Iptr_type,struct hshentry const*)); 33811894Speterstatic void insertline P((long,Iptr_type)); 33911894Speterstatic void snapshotline P((FILE*,Iptr_type)); 34011894Speter 3419Sjkh/* 3429Sjkh * `line' contains pointers to the lines in the currently `edited' file. 3439Sjkh * It is a 0-origin array that represents linelim-gapsize lines. 34411894Speter * line[0 .. gap-1] and line[gap+gapsize .. linelim-1] hold pointers to lines. 34511894Speter * line[gap .. gap+gapsize-1] contains garbage. 3469Sjkh * 3479Sjkh * Any @s in lines are duplicated. 3489Sjkh * Lines are terminated by \n, or (for a last partial line only) by single @. 3499Sjkh */ 3509Sjkhstatic Iptr_type *line; 35111894Speterstatic size_t gap, gapsize, linelim; 3529Sjkh 3539Sjkh static void 3549Sjkhinsertline(n, l) 35511894Speter long n; 3569Sjkh Iptr_type l; 3579Sjkh/* Before line N, insert line L. N is 0-origin. */ 3589Sjkh{ 3599Sjkh if (linelim-gapsize < n) 3609Sjkh editLineNumberOverflow(); 3619Sjkh if (!gapsize) 3629Sjkh line = 3639Sjkh !linelim ? 3649Sjkh tnalloc(Iptr_type, linelim = gapsize = 1024) 3659Sjkh : ( 3669Sjkh gap = gapsize = linelim, 3679Sjkh trealloc(Iptr_type, line, linelim <<= 1) 3689Sjkh ); 3699Sjkh if (n < gap) 3709Sjkh movelines(line+n+gapsize, line+n, gap-n); 3719Sjkh else if (gap < n) 3729Sjkh movelines(line+gap, line+gap+gapsize, n-gap); 3739Sjkh 3749Sjkh line[n] = l; 3759Sjkh gap = n + 1; 3769Sjkh gapsize--; 3779Sjkh} 3789Sjkh 3799Sjkh static void 3809Sjkhdeletelines(n, nlines) 38111894Speter long n, nlines; 3829Sjkh/* Delete lines N through N+NLINES-1. N is 0-origin. */ 3839Sjkh{ 38411894Speter long l = n + nlines; 3859Sjkh if (linelim-gapsize < l || l < n) 3869Sjkh editLineNumberOverflow(); 3879Sjkh if (l < gap) 3889Sjkh movelines(line+l+gapsize, line+l, gap-l); 3899Sjkh else if (gap < n) 3909Sjkh movelines(line+gap, line+gap+gapsize, n-gap); 3919Sjkh 3929Sjkh gap = n; 3939Sjkh gapsize += nlines; 3949Sjkh} 3959Sjkh 3969Sjkh static void 3979Sjkhsnapshotline(f, l) 3989Sjkh register FILE *f; 3999Sjkh register Iptr_type l; 4009Sjkh{ 4019Sjkh register int c; 4029Sjkh do { 4039Sjkh if ((c = *l++) == SDELIM && *l++ != SDELIM) 4049Sjkh return; 40511894Speter aputc_(c, f) 4069Sjkh } while (c != '\n'); 4079Sjkh} 4089Sjkh 4099Sjkh void 4109Sjkhsnapshotedit(f) 4119Sjkh FILE *f; 4129Sjkh/* Copy the current state of the edits to F. */ 4139Sjkh{ 4149Sjkh register Iptr_type *p, *lim, *l=line; 4159Sjkh for (p=l, lim=l+gap; p<lim; ) 4169Sjkh snapshotline(f, *p++); 4179Sjkh for (p+=gapsize, lim=l+linelim; p<lim; ) 4189Sjkh snapshotline(f, *p++); 4199Sjkh} 4209Sjkh 4219Sjkh static void 4229Sjkhfinisheditline(fin, fout, l, delta) 4239Sjkh RILE *fin; 4249Sjkh FILE *fout; 4259Sjkh Iptr_type l; 4269Sjkh struct hshentry const *delta; 4279Sjkh{ 42811894Speter fin->ptr = l; 42911894Speter if (expandline(fin, fout, delta, true, (FILE*)0, true) < 0) 4309Sjkh faterror("finisheditline internal error"); 4319Sjkh} 4329Sjkh 4339Sjkh void 4349Sjkhfinishedit(delta, outfile, done) 4359Sjkh struct hshentry const *delta; 4369Sjkh FILE *outfile; 4379Sjkh int done; 4389Sjkh/* 4399Sjkh * Doing expansion if DELTA is set, output the state of the edits to OUTFILE. 4409Sjkh * But do nothing unless DONE is set (which means we are on the last pass). 4419Sjkh */ 4429Sjkh{ 4439Sjkh if (done) { 4449Sjkh openfcopy(outfile); 4459Sjkh outfile = fcopy; 4469Sjkh if (!delta) 4479Sjkh snapshotedit(outfile); 4489Sjkh else { 4499Sjkh register Iptr_type *p, *lim, *l = line; 4509Sjkh register RILE *fin = finptr; 45111894Speter Iptr_type here = fin->ptr; 4529Sjkh for (p=l, lim=l+gap; p<lim; ) 4539Sjkh finisheditline(fin, outfile, *p++, delta); 4549Sjkh for (p+=gapsize, lim=l+linelim; p<lim; ) 4559Sjkh finisheditline(fin, outfile, *p++, delta); 45611894Speter fin->ptr = here; 4579Sjkh } 4589Sjkh } 4599Sjkh} 4609Sjkh 46111894Speter/* Open a temporary NAME for output, truncating any previous contents. */ 46211894Speter# define fopen_update_truncate(name) fopenSafer(name, FOPEN_W_WORK) 4639Sjkh#else /* !large_memory */ 46411894Speter static FILE * fopen_update_truncate P((char const*)); 4659Sjkh static FILE * 46611894Speterfopen_update_truncate(name) 46711894Speter char const *name; 4689Sjkh{ 46911894Speter if (bad_fopen_wplus && un_link(name) != 0) 47011894Speter efaterror(name); 47111894Speter return fopenSafer(name, FOPEN_WPLUS_WORK); 4729Sjkh} 4739Sjkh#endif 4749Sjkh 4759Sjkh 4769Sjkh void 4779Sjkhopenfcopy(f) 4789Sjkh FILE *f; 4799Sjkh{ 4809Sjkh if (!(fcopy = f)) { 48111894Speter if (!resultname) 48211894Speter resultname = maketemp(2); 48311894Speter if (!(fcopy = fopen_update_truncate(resultname))) 48411894Speter efaterror(resultname); 4859Sjkh } 4869Sjkh} 4879Sjkh 4889Sjkh 4899Sjkh#if !large_memory 4909Sjkh 49111894Speter static void swapeditfiles P((FILE*)); 4929Sjkh static void 4939Sjkhswapeditfiles(outfile) 4949Sjkh FILE *outfile; 49511894Speter/* Function: swaps resultname and editname, assigns fedit=fcopy, 4969Sjkh * and rewinds fedit for reading. Set fcopy to outfile if nonnull; 49711894Speter * otherwise, set fcopy to be resultname opened for reading and writing. 4989Sjkh */ 4999Sjkh{ 5009Sjkh char const *tmpptr; 5019Sjkh 5029Sjkh editline = 0; linecorr = 0; 50311894Speter Orewind(fcopy); 5049Sjkh fedit = fcopy; 50511894Speter tmpptr=editname; editname=resultname; resultname=tmpptr; 5069Sjkh openfcopy(outfile); 5079Sjkh} 5089Sjkh 5099Sjkh void 5109Sjkhsnapshotedit(f) 5119Sjkh FILE *f; 5129Sjkh/* Copy the current state of the edits to F. */ 5139Sjkh{ 51411894Speter finishedit((struct hshentry *)0, (FILE*)0, false); 5159Sjkh fastcopy(fedit, f); 5169Sjkh Irewind(fedit); 5179Sjkh} 5189Sjkh 5199Sjkh void 5209Sjkhfinishedit(delta, outfile, done) 5219Sjkh struct hshentry const *delta; 5229Sjkh FILE *outfile; 5239Sjkh int done; 5249Sjkh/* copy the rest of the edit file and close it (if it exists). 52511894Speter * if delta, perform keyword substitution at the same time. 5269Sjkh * If DONE is set, we are finishing the last pass. 5279Sjkh */ 5289Sjkh{ 5299Sjkh register RILE *fe; 5309Sjkh register FILE *fc; 5319Sjkh 5329Sjkh fe = fedit; 5339Sjkh if (fe) { 5349Sjkh fc = fcopy; 53511894Speter if (delta) { 53611894Speter while (1 < expandline(fe,fc,delta,false,(FILE*)0,true)) 5379Sjkh ; 5389Sjkh } else { 5399Sjkh fastcopy(fe,fc); 5409Sjkh } 5419Sjkh Ifclose(fe); 5429Sjkh } 5439Sjkh if (!done) 5449Sjkh swapeditfiles(outfile); 5459Sjkh} 5469Sjkh#endif 5479Sjkh 5489Sjkh 5499Sjkh 5509Sjkh#if large_memory 5519Sjkh# define copylines(upto,delta) (editline = (upto)) 5529Sjkh#else 55311894Speter static void copylines P((long,struct hshentry const*)); 5549Sjkh static void 55511894Spetercopylines(upto, delta) 55611894Speter register long upto; 5579Sjkh struct hshentry const *delta; 5589Sjkh/* 5599Sjkh * Copy input lines editline+1..upto from fedit to fcopy. 56011894Speter * If delta, keyword expansion is done simultaneously. 5619Sjkh * editline is updated. Rewinds a file only if necessary. 5629Sjkh */ 5639Sjkh{ 5649Sjkh register int c; 5659Sjkh declarecache; 5669Sjkh register FILE *fc; 5679Sjkh register RILE *fe; 5689Sjkh 5699Sjkh if (upto < editline) { 5709Sjkh /* swap files */ 57111894Speter finishedit((struct hshentry *)0, (FILE*)0, false); 5729Sjkh /* assumes edit only during last pass, from the beginning*/ 5739Sjkh } 5749Sjkh fe = fedit; 5759Sjkh fc = fcopy; 5769Sjkh if (editline < upto) 5779Sjkh if (delta) 5789Sjkh do { 57911894Speter if (expandline(fe,fc,delta,false,(FILE*)0,true) <= 1) 58011894Speter editLineNumberOverflow(); 5819Sjkh } while (++editline < upto); 5829Sjkh else { 5839Sjkh setupcache(fe); cache(fe); 5849Sjkh do { 5859Sjkh do { 58611894Speter cachegeteof_(c, editLineNumberOverflow();) 58711894Speter aputc_(c, fc) 5889Sjkh } while (c != '\n'); 5899Sjkh } while (++editline < upto); 5909Sjkh uncache(fe); 5919Sjkh } 5929Sjkh} 5939Sjkh#endif 5949Sjkh 5959Sjkh 5969Sjkh 5979Sjkh void 5989Sjkhxpandstring(delta) 5999Sjkh struct hshentry const *delta; 6009Sjkh/* Function: Reads a string terminated by SDELIM from finptr and writes it 6019Sjkh * to fcopy. Double SDELIM is replaced with single SDELIM. 6029Sjkh * Keyword expansion is performed with data from delta. 6039Sjkh * If foutptr is nonnull, the string is also copied unchanged to foutptr. 6049Sjkh */ 6059Sjkh{ 60611894Speter while (1 < expandline(finptr,fcopy,delta,true,foutptr,true)) 60711894Speter continue; 6089Sjkh} 6099Sjkh 6109Sjkh 6119Sjkh void 6129Sjkhcopystring() 6139Sjkh/* Function: copies a string terminated with a single SDELIM from finptr to 6149Sjkh * fcopy, replacing all double SDELIM with a single SDELIM. 6159Sjkh * If foutptr is nonnull, the string also copied unchanged to foutptr. 6169Sjkh * editline is incremented by the number of lines copied. 6179Sjkh * Assumption: next character read is first string character. 6189Sjkh */ 6199Sjkh{ register c; 6209Sjkh declarecache; 6219Sjkh register FILE *frew, *fcop; 6229Sjkh register int amidline; 6239Sjkh register RILE *fin; 6249Sjkh 6259Sjkh fin = finptr; 6269Sjkh setupcache(fin); cache(fin); 6279Sjkh frew = foutptr; 6289Sjkh fcop = fcopy; 6299Sjkh amidline = false; 6309Sjkh for (;;) { 63111894Speter GETC_(frew,c) 6329Sjkh switch (c) { 6339Sjkh case '\n': 6349Sjkh ++editline; 6359Sjkh ++rcsline; 6369Sjkh amidline = false; 6379Sjkh break; 6389Sjkh case SDELIM: 63911894Speter GETC_(frew,c) 6409Sjkh if (c != SDELIM) { 6419Sjkh /* end of string */ 6429Sjkh nextc = c; 6439Sjkh editline += amidline; 6449Sjkh uncache(fin); 6459Sjkh return; 6469Sjkh } 6479Sjkh /* fall into */ 6489Sjkh default: 6499Sjkh amidline = true; 6509Sjkh break; 6519Sjkh } 65211894Speter aputc_(c,fcop) 6539Sjkh } 6549Sjkh} 6559Sjkh 6569Sjkh 6579Sjkh void 6589Sjkhenterstring() 6599Sjkh/* Like copystring, except the string is put into the edit data structure. */ 6609Sjkh{ 6619Sjkh#if !large_memory 66211894Speter editname = 0; 6639Sjkh fedit = 0; 6649Sjkh editline = linecorr = 0; 66511894Speter resultname = maketemp(1); 66611894Speter if (!(fcopy = fopen_update_truncate(resultname))) 66711894Speter efaterror(resultname); 6689Sjkh copystring(); 6699Sjkh#else 6709Sjkh register int c; 6719Sjkh declarecache; 6729Sjkh register FILE *frew; 67311894Speter register long e, oe; 6749Sjkh register int amidline, oamidline; 6759Sjkh register Iptr_type optr; 6769Sjkh register RILE *fin; 6779Sjkh 6789Sjkh e = 0; 6799Sjkh gap = 0; 6809Sjkh gapsize = linelim; 6819Sjkh fin = finptr; 6829Sjkh setupcache(fin); cache(fin); 6839Sjkh advise_access(fin, MADV_NORMAL); 6849Sjkh frew = foutptr; 6859Sjkh amidline = false; 6869Sjkh for (;;) { 68711894Speter optr = cacheptr(); 68811894Speter GETC_(frew,c) 6899Sjkh oamidline = amidline; 6909Sjkh oe = e; 6919Sjkh switch (c) { 6929Sjkh case '\n': 6939Sjkh ++e; 6949Sjkh ++rcsline; 6959Sjkh amidline = false; 6969Sjkh break; 6979Sjkh case SDELIM: 69811894Speter GETC_(frew,c) 6999Sjkh if (c != SDELIM) { 7009Sjkh /* end of string */ 7019Sjkh nextc = c; 7029Sjkh editline = e + amidline; 7039Sjkh linecorr = 0; 7049Sjkh uncache(fin); 7059Sjkh return; 7069Sjkh } 7079Sjkh /* fall into */ 7089Sjkh default: 7099Sjkh amidline = true; 7109Sjkh break; 7119Sjkh } 7129Sjkh if (!oamidline) 7139Sjkh insertline(oe, optr); 7149Sjkh } 7159Sjkh#endif 7169Sjkh} 7179Sjkh 7189Sjkh 7199Sjkh 7209Sjkh 7219Sjkh void 7229Sjkh#if large_memory 7239Sjkhedit_string() 7249Sjkh#else 7259Sjkh editstring(delta) 7269Sjkh struct hshentry const *delta; 7279Sjkh#endif 7289Sjkh/* 7299Sjkh * Read an edit script from finptr and applies it to the edit file. 7309Sjkh#if !large_memory 7319Sjkh * The result is written to fcopy. 73211894Speter * If delta, keyword expansion is performed simultaneously. 7339Sjkh * If running out of lines in fedit, fedit and fcopy are swapped. 73411894Speter * editname is the name of the file that goes with fedit. 7359Sjkh#endif 7369Sjkh * If foutptr is set, the edit script is also copied verbatim to foutptr. 7379Sjkh * Assumes that all these files are open. 73811894Speter * resultname is the name of the file that goes with fcopy. 7399Sjkh * Assumes the next input character from finptr is the first character of 7409Sjkh * the edit script. Resets nextc on exit. 7419Sjkh */ 7429Sjkh{ 7439Sjkh int ed; /* editor command */ 7449Sjkh register int c; 7459Sjkh declarecache; 7469Sjkh register FILE *frew; 7479Sjkh# if !large_memory 7489Sjkh register FILE *f; 74911894Speter long line_lim = LONG_MAX; 7509Sjkh register RILE *fe; 7519Sjkh# endif 75211894Speter register long i; 7539Sjkh register RILE *fin; 7549Sjkh# if large_memory 75511894Speter register long j; 7569Sjkh# endif 7579Sjkh struct diffcmd dc; 7589Sjkh 7599Sjkh editline += linecorr; linecorr=0; /*correct line number*/ 7609Sjkh frew = foutptr; 7619Sjkh fin = finptr; 7629Sjkh setupcache(fin); 7639Sjkh initdiffcmd(&dc); 7649Sjkh while (0 <= (ed = getdiffcmd(fin,true,frew,&dc))) 7659Sjkh#if !large_memory 7669Sjkh if (line_lim <= dc.line1) 7679Sjkh editLineNumberOverflow(); 7689Sjkh else 7699Sjkh#endif 7709Sjkh if (!ed) { 7719Sjkh copylines(dc.line1-1, delta); 7729Sjkh /* skip over unwanted lines */ 7739Sjkh i = dc.nlines; 7749Sjkh linecorr -= i; 7759Sjkh editline += i; 7769Sjkh# if large_memory 7779Sjkh deletelines(editline+linecorr, i); 7789Sjkh# else 7799Sjkh fe = fedit; 7809Sjkh do { 7819Sjkh /*skip next line*/ 7829Sjkh do { 78311894Speter Igeteof_(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } ) 7849Sjkh } while (c != '\n'); 7859Sjkh } while (--i); 7869Sjkh# endif 7879Sjkh } else { 78811894Speter /* Copy lines without deleting any. */ 78911894Speter copylines(dc.line1, delta); 7909Sjkh i = dc.nlines; 7919Sjkh# if large_memory 7929Sjkh j = editline+linecorr; 7939Sjkh# endif 7949Sjkh linecorr += i; 7959Sjkh#if !large_memory 7969Sjkh f = fcopy; 7979Sjkh if (delta) 7989Sjkh do { 79911894Speter switch (expandline(fin,f,delta,true,frew,true)){ 8009Sjkh case 0: case 1: 8019Sjkh if (i==1) 8029Sjkh return; 8039Sjkh /* fall into */ 8049Sjkh case -1: 8059Sjkh editEndsPrematurely(); 8069Sjkh } 8079Sjkh } while (--i); 8089Sjkh else 8099Sjkh#endif 8109Sjkh { 8119Sjkh cache(fin); 8129Sjkh do { 8139Sjkh# if large_memory 81411894Speter insertline(j++, cacheptr()); 8159Sjkh# endif 8169Sjkh for (;;) { 81711894Speter GETC_(frew, c) 8189Sjkh if (c==SDELIM) { 81911894Speter GETC_(frew, c) 8209Sjkh if (c!=SDELIM) { 8219Sjkh if (--i) 8229Sjkh editEndsPrematurely(); 8239Sjkh nextc = c; 8249Sjkh uncache(fin); 8259Sjkh return; 8269Sjkh } 8279Sjkh } 82811894Speter# if !large_memory 82911894Speter aputc_(c, f) 83011894Speter# endif 83111894Speter if (c == '\n') 83211894Speter break; 8339Sjkh } 8349Sjkh ++rcsline; 8359Sjkh } while (--i); 8369Sjkh uncache(fin); 8379Sjkh } 8389Sjkh } 8399Sjkh} 8409Sjkh 8419Sjkh 8429Sjkh 8439Sjkh/* The rest is for keyword expansion */ 8449Sjkh 8459Sjkh 8469Sjkh 8479Sjkh int 84811894Speterexpandline(infile, outfile, delta, delimstuffed, frewfile, dolog) 8499Sjkh RILE *infile; 8509Sjkh FILE *outfile, *frewfile; 8519Sjkh struct hshentry const *delta; 85211894Speter int delimstuffed, dolog; 8539Sjkh/* 8549Sjkh * Read a line from INFILE and write it to OUTFILE. 85511894Speter * Do keyword expansion with data from DELTA. 8569Sjkh * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM. 8579Sjkh * If FREWFILE is set, copy the line unchanged to FREWFILE. 8589Sjkh * DELIMSTUFFED must be true if FREWFILE is set. 85911894Speter * Append revision history to log only if DOLOG is set. 8609Sjkh * Yields -1 if no data is copied, 0 if an incomplete line is copied, 8619Sjkh * 2 if a complete line is copied; adds 1 to yield if expansion occurred. 8629Sjkh */ 8639Sjkh{ 8649Sjkh register c; 8659Sjkh declarecache; 8669Sjkh register FILE *out, *frew; 8679Sjkh register char * tp; 8689Sjkh register int e, ds, r; 8699Sjkh char const *tlim; 8709Sjkh static struct buf keyval; 8719Sjkh enum markers matchresult; 8729Sjkh 8739Sjkh setupcache(infile); cache(infile); 8749Sjkh out = outfile; 8759Sjkh frew = frewfile; 8769Sjkh ds = delimstuffed; 8779Sjkh bufalloc(&keyval, keylength+3); 8789Sjkh e = 0; 8799Sjkh r = -1; 8809Sjkh 8819Sjkh for (;;) { 88211894Speter if (ds) 88311894Speter GETC_(frew, c) 88411894Speter else 88511894Speter cachegeteof_(c, goto uncache_exit;) 8869Sjkh for (;;) { 8879Sjkh switch (c) { 8889Sjkh case SDELIM: 8899Sjkh if (ds) { 89011894Speter GETC_(frew, c) 8919Sjkh if (c != SDELIM) { 8929Sjkh /* end of string */ 8939Sjkh nextc=c; 8949Sjkh goto uncache_exit; 8959Sjkh } 8969Sjkh } 8979Sjkh /* fall into */ 8989Sjkh default: 89911894Speter aputc_(c,out) 9009Sjkh r = 0; 9019Sjkh break; 9029Sjkh 9039Sjkh case '\n': 9049Sjkh rcsline += ds; 90511894Speter aputc_(c,out) 9069Sjkh r = 2; 9079Sjkh goto uncache_exit; 9089Sjkh 9099Sjkh case KDELIM: 9109Sjkh r = 0; 9119Sjkh /* check for keyword */ 9129Sjkh /* first, copy a long enough string into keystring */ 9139Sjkh tp = keyval.string; 9149Sjkh *tp++ = KDELIM; 9159Sjkh for (;;) { 91611894Speter if (ds) 91711894Speter GETC_(frew, c) 91811894Speter else 91911894Speter cachegeteof_(c, goto keystring_eof;) 92011894Speter if (tp <= &keyval.string[keylength]) 9219Sjkh switch (ctab[c]) { 9229Sjkh case LETTER: case Letter: 9239Sjkh *tp++ = c; 9249Sjkh continue; 9259Sjkh default: 9269Sjkh break; 9279Sjkh } 9289Sjkh break; 9299Sjkh } 9309Sjkh *tp++ = c; *tp = '\0'; 9319Sjkh matchresult = trymatch(keyval.string+1); 9329Sjkh if (matchresult==Nomatch) { 9339Sjkh tp[-1] = 0; 9349Sjkh aputs(keyval.string, out); 9359Sjkh continue; /* last c handled properly */ 9369Sjkh } 9379Sjkh 9389Sjkh /* Now we have a keyword terminated with a K/VDELIM */ 9399Sjkh if (c==VDELIM) { 9409Sjkh /* try to find closing KDELIM, and replace value */ 9419Sjkh tlim = keyval.string + keyval.size; 9429Sjkh for (;;) { 94311894Speter if (ds) 94411894Speter GETC_(frew, c) 94511894Speter else 94611894Speter cachegeteof_(c, goto keystring_eof;) 9479Sjkh if (c=='\n' || c==KDELIM) 9489Sjkh break; 9499Sjkh *tp++ =c; 9509Sjkh if (tlim <= tp) 9519Sjkh tp = bufenlarge(&keyval, &tlim); 9529Sjkh if (c==SDELIM && ds) { /*skip next SDELIM */ 95311894Speter GETC_(frew, c) 9549Sjkh if (c != SDELIM) { 9559Sjkh /* end of string before closing KDELIM or newline */ 9569Sjkh nextc = c; 9579Sjkh goto keystring_eof; 9589Sjkh } 9599Sjkh } 9609Sjkh } 9619Sjkh if (c!=KDELIM) { 9629Sjkh /* couldn't find closing KDELIM -- give up */ 9639Sjkh *tp = 0; 9649Sjkh aputs(keyval.string, out); 9659Sjkh continue; /* last c handled properly */ 9669Sjkh } 9679Sjkh } 9689Sjkh /* now put out the new keyword value */ 96911894Speter uncache(infile); 97011894Speter keyreplace(matchresult, delta, ds, infile, out, dolog); 97111894Speter cache(infile); 9729Sjkh e = 1; 9739Sjkh break; 9749Sjkh } 9759Sjkh break; 9769Sjkh } 9779Sjkh } 9789Sjkh 9799Sjkh keystring_eof: 9809Sjkh *tp = 0; 9819Sjkh aputs(keyval.string, out); 9829Sjkh uncache_exit: 9839Sjkh uncache(infile); 9849Sjkh return r + e; 9859Sjkh} 9869Sjkh 9879Sjkh 98811894Speter static void 98911894Speterescape_string(out, s) 99011894Speter register FILE *out; 99111894Speter register char const *s; 99211894Speter/* Output to OUT the string S, escaping chars that would break `ci -k'. */ 99311894Speter{ 99411894Speter register char c; 99511894Speter for (;;) 99611894Speter switch ((c = *s++)) { 99711894Speter case 0: return; 99811894Speter case '\t': aputs("\\t", out); break; 99911894Speter case '\n': aputs("\\n", out); break; 100011894Speter case ' ': aputs("\\040", out); break; 100111894Speter case KDELIM: aputs("\\044", out); break; 100211894Speter case '\\': if (VERSION(5)<=RCSversion) {aputs("\\\\", out); break;} 100311894Speter /* fall into */ 100411894Speter default: aputc_(c, out) break; 100511894Speter } 100611894Speter} 100711894Speter 10089Sjkhchar const ciklog[ciklogsize] = "checked in with -k by "; 10099Sjkh 10109Sjkh static void 101111894Speterkeyreplace(marker, delta, delimstuffed, infile, out, dolog) 10129Sjkh enum markers marker; 10139Sjkh register struct hshentry const *delta; 101411894Speter int delimstuffed; 101511894Speter RILE *infile; 10169Sjkh register FILE *out; 101711894Speter int dolog; 10189Sjkh/* function: outputs the keyword value(s) corresponding to marker. 10199Sjkh * Attributes are derived from delta. 10209Sjkh */ 10219Sjkh{ 10229Sjkh register char const *sp, *cp, *date; 102311894Speter register int c; 10249Sjkh register size_t cs, cw, ls; 10259Sjkh char const *sp1; 102611894Speter char datebuf[datesize + zonelenmax]; 10279Sjkh int RCSv; 102811894Speter int exp; 10299Sjkh 10309Sjkh sp = Keyword[(int)marker]; 103111894Speter exp = Expand; 103211894Speter date = delta->date; 103311894Speter RCSv = RCSversion; 10349Sjkh 103511894Speter if (exp != VAL_EXPAND) 103611894Speter aprintf(out, "%c%s", KDELIM, sp); 103711894Speter if (exp != KEY_EXPAND) { 10389Sjkh 103911894Speter if (exp != VAL_EXPAND) 104011894Speter aprintf(out, "%c%c", VDELIM, 10419Sjkh marker==Log && RCSv<VERSION(5) ? '\t' : ' ' 10429Sjkh ); 10439Sjkh 104411894Speter switch (marker) { 104511894Speter case Author: 10469Sjkh aputs(delta->author, out); 10479Sjkh break; 104811894Speter case Date: 10499Sjkh aputs(date2str(date,datebuf), out); 10509Sjkh break; 105111894Speter case Id: 105225699Speter case LocalId: 105311894Speter case Header: 105455808Speter case CVSHeader: 105525699Speter if (marker == Id || RCSv < VERSION(4) || 105625699Speter (marker == LocalId && LocalIdMode == Id)) 105725699Speter escape_string(out, basefilename(RCSname)); 105825699Speter else if (marker == CVSHeader || 105925699Speter (marker == LocalId && LocalIdMode == CVSHeader)) 106025699Speter escape_string(out, getfullCVSname()); 106125699Speter else 106225699Speter escape_string(out, getfullRCSname()); 106311894Speter aprintf(out, " %s %s %s %s", 10649Sjkh delta->num, 10659Sjkh date2str(date, datebuf), 10669Sjkh delta->author, 10679Sjkh RCSv==VERSION(3) && delta->lockedby ? "Locked" 10689Sjkh : delta->state 10699Sjkh ); 107011894Speter if (delta->lockedby) 10719Sjkh if (VERSION(5) <= RCSv) { 107211894Speter if (locker_expansion || exp==KEYVALLOCK_EXPAND) 10739Sjkh aprintf(out, " %s", delta->lockedby); 10749Sjkh } else if (RCSv == VERSION(4)) 10759Sjkh aprintf(out, " Locker: %s", delta->lockedby); 10769Sjkh break; 107711894Speter case Locker: 10789Sjkh if (delta->lockedby) 10799Sjkh if ( 10809Sjkh locker_expansion 108111894Speter || exp == KEYVALLOCK_EXPAND 10829Sjkh || RCSv <= VERSION(4) 10839Sjkh ) 10849Sjkh aputs(delta->lockedby, out); 10859Sjkh break; 108611894Speter case Log: 108711894Speter case RCSfile: 108811894Speter escape_string(out, basefilename(RCSname)); 10899Sjkh break; 109011894Speter case Name: 109111894Speter if (delta->name) 109211894Speter aputs(delta->name, out); 109311894Speter break; 109411894Speter case Revision: 10959Sjkh aputs(delta->num, out); 10969Sjkh break; 109711894Speter case Source: 109811894Speter escape_string(out, getfullRCSname()); 10999Sjkh break; 110011894Speter case State: 11019Sjkh aputs(delta->state, out); 11029Sjkh break; 110311894Speter default: 11049Sjkh break; 110511894Speter } 110611894Speter if (exp != VAL_EXPAND) 11079Sjkh afputc(' ', out); 11089Sjkh } 110911894Speter if (exp != VAL_EXPAND) 111011894Speter afputc(KDELIM, out); 111111894Speter 111211894Speter if (marker == Log && dolog) { 111311894Speter struct buf leader; 111411894Speter 11159Sjkh sp = delta->log.string; 11169Sjkh ls = delta->log.size; 11179Sjkh if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1)) 11189Sjkh return; 111911894Speter bufautobegin(&leader); 112011894Speter if (RCSversion < VERSION(5)) { 112111894Speter cp = Comment.string; 112211894Speter cs = Comment.size; 112311894Speter } else { 112411894Speter int kdelim_found = 0; 112511894Speter Ioffset_type chars_read = Itell(infile); 112611894Speter declarecache; 112711894Speter setupcache(infile); cache(infile); 112811894Speter 112911894Speter c = 0; /* Pacify `gcc -Wall'. */ 113011894Speter 113111894Speter /* 113211894Speter * Back up to the start of the current input line, 113311894Speter * setting CS to the number of characters before `$Log'. 113411894Speter */ 113511894Speter cs = 0; 113611894Speter for (;;) { 113711894Speter if (!--chars_read) 113811894Speter goto done_backing_up; 113911894Speter cacheunget_(infile, c) 114011894Speter if (c == '\n') 114111894Speter break; 114211894Speter if (c == SDELIM && delimstuffed) { 114311894Speter if (!--chars_read) 114411894Speter break; 114511894Speter cacheunget_(infile, c) 114611894Speter if (c != SDELIM) { 114711894Speter cacheget_(c) 114811894Speter break; 114911894Speter } 115011894Speter } 115111894Speter cs += kdelim_found; 115211894Speter kdelim_found |= c==KDELIM; 115311894Speter } 115411894Speter cacheget_(c) 115511894Speter done_backing_up:; 115611894Speter 115711894Speter /* Copy characters before `$Log' into LEADER. */ 115811894Speter bufalloc(&leader, cs); 115911894Speter cp = leader.string; 116011894Speter for (cw = 0; cw < cs; cw++) { 116111894Speter leader.string[cw] = c; 116211894Speter if (c == SDELIM && delimstuffed) 116311894Speter cacheget_(c) 116411894Speter cacheget_(c) 116511894Speter } 116611894Speter 116711894Speter /* Convert traditional C or Pascal leader to ` *'. */ 116811894Speter for (cw = 0; cw < cs; cw++) 116911894Speter if (ctab[(unsigned char) cp[cw]] != SPACE) 117011894Speter break; 117111894Speter if ( 117211894Speter cw+1 < cs 117311894Speter && cp[cw+1] == '*' 117411894Speter && (cp[cw] == '/' || cp[cw] == '(') 117511894Speter ) { 117611894Speter size_t i = cw+1; 117711894Speter for (;;) 117811894Speter if (++i == cs) { 117911894Speter warn( 118011894Speter "`%c* $Log' is obsolescent; use ` * $Log'.", 118111894Speter cp[cw] 118211894Speter ); 118311894Speter leader.string[cw] = ' '; 118411894Speter break; 118511894Speter } else if (ctab[(unsigned char) cp[i]] != SPACE) 118611894Speter break; 118711894Speter } 118811894Speter 118911894Speter /* Skip `$Log ... $' string. */ 119011894Speter do { 119111894Speter cacheget_(c) 119211894Speter } while (c != KDELIM); 119311894Speter uncache(infile); 119411894Speter } 11959Sjkh afputc('\n', out); 11969Sjkh awrite(cp, cs, out); 119711894Speter sp1 = date2str(date, datebuf); 119811894Speter if (VERSION(5) <= RCSv) { 119911894Speter aprintf(out, "Revision %s %s %s", 120011894Speter delta->num, sp1, delta->author 120111894Speter ); 120211894Speter } else { 120311894Speter /* oddity: 2 spaces between date and time, not 1 as usual */ 120411894Speter sp1 = strchr(sp1, ' '); 120511894Speter aprintf(out, "Revision %s %.*s %s %s", 120611894Speter delta->num, (int)(sp1-datebuf), datebuf, sp1, 120711894Speter delta->author 120811894Speter ); 120911894Speter } 12109Sjkh /* Do not include state: it may change and is not updated. */ 121111894Speter cw = cs; 12129Sjkh if (VERSION(5) <= RCSv) 12139Sjkh for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw) 121411894Speter continue; 12159Sjkh for (;;) { 12169Sjkh afputc('\n', out); 12179Sjkh awrite(cp, cw, out); 12189Sjkh if (!ls) 12199Sjkh break; 12209Sjkh --ls; 12219Sjkh c = *sp++; 12229Sjkh if (c != '\n') { 12239Sjkh awrite(cp+cw, cs-cw, out); 12249Sjkh do { 12259Sjkh afputc(c,out); 12269Sjkh if (!ls) 12279Sjkh break; 12289Sjkh --ls; 12299Sjkh c = *sp++; 12309Sjkh } while (c != '\n'); 12319Sjkh } 12329Sjkh } 123311894Speter bufautoend(&leader); 12349Sjkh } 12359Sjkh} 12369Sjkh 12379Sjkh#if has_readlink 123811894Speter static int resolve_symlink P((struct buf*)); 12399Sjkh static int 12409Sjkhresolve_symlink(L) 12419Sjkh struct buf *L; 12429Sjkh/* 12439Sjkh * If L is a symbolic link, resolve it to the name that it points to. 12449Sjkh * If unsuccessful, set errno and yield -1. 12459Sjkh * If it points to an existing file, yield 1. 12469Sjkh * Otherwise, set errno=ENOENT and yield 0. 12479Sjkh */ 12489Sjkh{ 12499Sjkh char *b, a[SIZEABLE_PATH]; 12509Sjkh int e; 12519Sjkh size_t s; 12529Sjkh ssize_t r; 12539Sjkh struct buf bigbuf; 125411894Speter int linkcount = MAXSYMLINKS; 12559Sjkh 12569Sjkh b = a; 12579Sjkh s = sizeof(a); 12589Sjkh bufautobegin(&bigbuf); 12599Sjkh while ((r = readlink(L->string,b,s)) != -1) 12609Sjkh if (r == s) { 12619Sjkh bufalloc(&bigbuf, s<<1); 12629Sjkh b = bigbuf.string; 12639Sjkh s = bigbuf.size; 126411894Speter } else if (!linkcount--) { 126511894Speter# ifndef ELOOP 126611894Speter /* 126711894Speter * Some pedantic Posix 1003.1-1990 hosts have readlink 126811894Speter * but not ELOOP. Approximate ELOOP with EMLINK. 126911894Speter */ 127011894Speter# define ELOOP EMLINK 127111894Speter# endif 12729Sjkh errno = ELOOP; 12739Sjkh return -1; 12749Sjkh } else { 12759Sjkh /* Splice symbolic link into L. */ 12769Sjkh b[r] = '\0'; 127711894Speter L->string[ 127811894Speter ROOTPATH(b) ? 0 : basefilename(L->string) - L->string 127911894Speter ] = '\0'; 12809Sjkh bufscat(L, b); 12819Sjkh } 12829Sjkh e = errno; 12839Sjkh bufautoend(&bigbuf); 12849Sjkh errno = e; 12859Sjkh switch (e) { 128611894Speter case readlink_isreg_errno: return 1; 12879Sjkh case ENOENT: return 0; 12889Sjkh default: return -1; 12899Sjkh } 12909Sjkh} 12919Sjkh#endif 12929Sjkh 12939Sjkh RILE * 12949Sjkhrcswriteopen(RCSbuf, status, mustread) 12959Sjkh struct buf *RCSbuf; 12969Sjkh struct stat *status; 12979Sjkh int mustread; 12989Sjkh/* 129911894Speter * Create the lock file corresponding to RCSBUF. 130011894Speter * Then try to open RCSBUF for reading and yield its RILE* descriptor. 13019Sjkh * Put its status into *STATUS too. 13029Sjkh * MUSTREAD is true if the file must already exist, too. 13039Sjkh * If all goes well, discard any previously acquired locks, 130411894Speter * and set fdlock to the file descriptor of the RCS lockfile. 13059Sjkh */ 13069Sjkh{ 13079Sjkh register char *tp; 130811894Speter register char const *sp, *RCSpath, *x; 13099Sjkh RILE *f; 13109Sjkh size_t l; 131111894Speter int e, exists, fdesc, fdescSafer, r, waslocked; 13129Sjkh struct buf *dirt; 13139Sjkh struct stat statbuf; 13149Sjkh 131511894Speter waslocked = 0 <= fdlock; 13169Sjkh exists = 13179Sjkh# if has_readlink 13189Sjkh resolve_symlink(RCSbuf); 13199Sjkh# else 13209Sjkh stat(RCSbuf->string, &statbuf) == 0 ? 1 13219Sjkh : errno==ENOENT ? 0 : -1; 13229Sjkh# endif 132311894Speter if (exists < (mustread|waslocked)) 13249Sjkh /* 13259Sjkh * There's an unusual problem with the RCS file; 13269Sjkh * or the RCS file doesn't exist, 13279Sjkh * and we must read or we already have a lock elsewhere. 13289Sjkh */ 13299Sjkh return 0; 13309Sjkh 133111894Speter RCSpath = RCSbuf->string; 133211894Speter sp = basefilename(RCSpath); 133311894Speter l = sp - RCSpath; 133411894Speter dirt = &dirtpname[waslocked]; 133511894Speter bufscpy(dirt, RCSpath); 13369Sjkh tp = dirt->string + l; 133711894Speter x = rcssuffix(RCSpath); 13389Sjkh# if has_readlink 13399Sjkh if (!x) { 134011894Speter error("symbolic link to non RCS file `%s'", RCSpath); 13419Sjkh errno = EINVAL; 13429Sjkh return 0; 13439Sjkh } 13449Sjkh# endif 13459Sjkh if (*sp == *x) { 134611894Speter error("RCS pathname `%s' incompatible with suffix `%s'", sp, x); 13479Sjkh errno = EINVAL; 13489Sjkh return 0; 13499Sjkh } 135011894Speter /* Create a lock filename that is a function of the RCS filename. */ 13519Sjkh if (*x) { 13529Sjkh /* 13539Sjkh * The suffix is nonempty. 13549Sjkh * The lock filename is the first char of of the suffix, 13559Sjkh * followed by the RCS filename with last char removed. E.g.: 13569Sjkh * foo,v RCS filename with suffix ,v 13579Sjkh * ,foo, lock filename 13589Sjkh */ 13599Sjkh *tp++ = *x; 13609Sjkh while (*sp) 13619Sjkh *tp++ = *sp++; 13629Sjkh *--tp = 0; 13639Sjkh } else { 13649Sjkh /* 13659Sjkh * The suffix is empty. 13669Sjkh * The lock filename is the RCS filename 13679Sjkh * with last char replaced by '_'. 13689Sjkh */ 13699Sjkh while ((*tp++ = *sp++)) 137011894Speter continue; 13719Sjkh tp -= 2; 13729Sjkh if (*tp == '_') { 137311894Speter error("RCS pathname `%s' ends with `%c'", RCSpath, *tp); 13749Sjkh errno = EINVAL; 13759Sjkh return 0; 13769Sjkh } 13779Sjkh *tp = '_'; 13789Sjkh } 13799Sjkh 138011894Speter sp = dirt->string; 13819Sjkh 13829Sjkh f = 0; 13839Sjkh 13849Sjkh /* 13859Sjkh * good news: 138611894Speter * open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY) 138711894Speter * is atomic according to Posix 1003.1-1990. 13889Sjkh * bad news: 13899Sjkh * NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990. 13909Sjkh * good news: 139111894Speter * (O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity 139211894Speter * even with NFS. 13939Sjkh * bad news: 139411894Speter * If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't 139511894Speter * guarantee atomicity. 13969Sjkh * good news: 13979Sjkh * Root-over-the-wire NFS access is rare for security reasons. 13989Sjkh * This bug has never been reported in practice with RCS. 13999Sjkh * So we don't worry about this bug. 14009Sjkh * 14019Sjkh * An even rarer NFS bug can occur when clients retry requests. 140211894Speter * This can happen in the usual case of NFS over UDP. 140311894Speter * Suppose client A releases a lock by renaming ",f," to "f,v" at 140411894Speter * about the same time that client B obtains a lock by creating ",f,", 14059Sjkh * and suppose A's first rename request is delayed, so A reissues it. 14069Sjkh * The sequence of events might be: 14079Sjkh * A sends rename(",f,", "f,v") 14089Sjkh * B sends create(",f,") 14099Sjkh * A sends retry of rename(",f,", "f,v") 14109Sjkh * server receives, does, and acknowledges A's first rename() 14119Sjkh * A receives acknowledgment, and its RCS program exits 14129Sjkh * server receives, does, and acknowledges B's create() 14139Sjkh * server receives, does, and acknowledges A's retry of rename() 14149Sjkh * This not only wrongly deletes B's lock, it removes the RCS file! 14159Sjkh * Most NFS implementations have idempotency caches that usually prevent 14169Sjkh * this scenario, but such caches are finite and can be overrun. 141711894Speter * This problem afflicts not only RCS, which uses open() and rename() 141811894Speter * to get and release locks; it also afflicts the traditional 14199Sjkh * Unix method of using link() and unlink() to get and release locks, 142011894Speter * and the less traditional method of using mkdir() and rmdir(). 142111894Speter * There is no easy workaround. 14229Sjkh * Any new method based on lockf() seemingly would be incompatible with 14239Sjkh * the old methods; besides, lockf() is notoriously buggy under NFS. 14249Sjkh * Since this problem afflicts scads of Unix programs, but is so rare 14259Sjkh * that nobody seems to be worried about it, we won't worry either. 14269Sjkh */ 14279Sjkh# if !open_can_creat 142811894Speter# define create(f) creat(f, OPEN_CREAT_READONLY) 14299Sjkh# else 143011894Speter# define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY) 14319Sjkh# endif 14329Sjkh 14339Sjkh catchints(); 14349Sjkh ignoreints(); 14359Sjkh 14369Sjkh /* 14379Sjkh * Create a lock file for an RCS file. This should be atomic, i.e. 14389Sjkh * if two processes try it simultaneously, at most one should succeed. 14399Sjkh */ 14409Sjkh seteid(); 14419Sjkh fdesc = create(sp); 144211894Speter fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr. */ 14439Sjkh e = errno; 14449Sjkh setrid(); 14459Sjkh 144611894Speter if (0 <= fdesc) 144711894Speter dirtpmaker[0] = effective; 144811894Speter 144911894Speter if (fdescSafer < 0) { 145011894Speter if (e == EACCES && stat(sp,&statbuf) == 0) 14519Sjkh /* The RCS file is busy. */ 14529Sjkh e = EEXIST; 14539Sjkh } else { 14549Sjkh e = ENOENT; 14559Sjkh if (exists) { 145611894Speter f = Iopen(RCSpath, FOPEN_RB, status); 14579Sjkh e = errno; 145811894Speter if (f && waslocked) { 14599Sjkh /* Discard the previous lock in favor of this one. */ 146011894Speter ORCSclose(); 14619Sjkh seteid(); 146211894Speter r = un_link(lockname); 146311894Speter e = errno; 14649Sjkh setrid(); 14659Sjkh if (r != 0) 146611894Speter enfaterror(e, lockname); 146711894Speter bufscpy(&dirtpname[lockdirtp_index], sp); 14689Sjkh } 14699Sjkh } 147011894Speter fdlock = fdescSafer; 14719Sjkh } 14729Sjkh 14739Sjkh restoreints(); 14749Sjkh 14759Sjkh errno = e; 14769Sjkh return f; 14779Sjkh} 14789Sjkh 14799Sjkh void 14809Sjkhkeepdirtemp(name) 14819Sjkh char const *name; 14829Sjkh/* Do not unlink name, either because it's not there any more, 14839Sjkh * or because it has already been unlinked. 14849Sjkh */ 14859Sjkh{ 14869Sjkh register int i; 14879Sjkh for (i=DIRTEMPNAMES; 0<=--i; ) 148811894Speter if (dirtpname[i].string == name) { 148911894Speter dirtpmaker[i] = notmade; 14909Sjkh return; 14919Sjkh } 14929Sjkh faterror("keepdirtemp"); 14939Sjkh} 14949Sjkh 14959Sjkh char const * 149611894Spetermakedirtemp(isworkfile) 149711894Speter int isworkfile; 14989Sjkh/* 149911894Speter * Create a unique pathname and store it into dirtpname. 150011894Speter * Because of storage in tpnames, dirtempunlink() can unlink the file later. 150111894Speter * Return a pointer to the pathname created. 150211894Speter * If ISWORKFILE is 1, put it into the working file's directory; 150311894Speter * if 0, put the unique file in RCSfile's directory. 15049Sjkh */ 15059Sjkh{ 15069Sjkh register char *tp, *np; 15079Sjkh register size_t dl; 15089Sjkh register struct buf *bn; 150911894Speter register char const *name = isworkfile ? workname : RCSname; 151076301Skris# if has_mktemp 151176301Skris int fd; 151276301Skris# endif 15139Sjkh 151411894Speter dl = basefilename(name) - name; 151511894Speter bn = &dirtpname[newRCSdirtp_index + isworkfile]; 15169Sjkh bufalloc(bn, 15179Sjkh# if has_mktemp 15189Sjkh dl + 9 15199Sjkh# else 15209Sjkh strlen(name) + 3 15219Sjkh# endif 15229Sjkh ); 15239Sjkh bufscpy(bn, name); 15249Sjkh np = tp = bn->string; 15259Sjkh tp += dl; 15269Sjkh *tp++ = '_'; 152711894Speter *tp++ = '0'+isworkfile; 15289Sjkh catchints(); 15299Sjkh# if has_mktemp 15309Sjkh VOID strcpy(tp, "XXXXXX"); 153176301Skris fd = mkstemp(np); 153276301Skris if (fd < 0 || !*np) 153311894Speter faterror("can't make temporary pathname `%.*s_%cXXXXXX'", 153411894Speter (int)dl, name, '0'+isworkfile 15359Sjkh ); 153676301Skris close(fd); 15379Sjkh# else 15389Sjkh /* 15399Sjkh * Posix 1003.1-1990 has no reliable way 15409Sjkh * to create a unique file in a named directory. 154111894Speter * We fudge here. If the filename is abcde, 15429Sjkh * the temp filename is _Ncde where N is a digit. 15439Sjkh */ 15449Sjkh name += dl; 15459Sjkh if (*name) name++; 15469Sjkh if (*name) name++; 15479Sjkh VOID strcpy(tp, name); 15489Sjkh# endif 154911894Speter dirtpmaker[newRCSdirtp_index + isworkfile] = real; 15509Sjkh return np; 15519Sjkh} 15529Sjkh 15539Sjkh void 15549Sjkhdirtempunlink() 15559Sjkh/* Clean up makedirtemp() files. May be invoked by signal handler. */ 15569Sjkh{ 15579Sjkh register int i; 15589Sjkh enum maker m; 15599Sjkh 15609Sjkh for (i = DIRTEMPNAMES; 0 <= --i; ) 156111894Speter if ((m = dirtpmaker[i]) != notmade) { 15629Sjkh if (m == effective) 15639Sjkh seteid(); 156411894Speter VOID un_link(dirtpname[i].string); 15659Sjkh if (m == effective) 15669Sjkh setrid(); 156711894Speter dirtpmaker[i] = notmade; 15689Sjkh } 15699Sjkh} 15709Sjkh 15719Sjkh 15729Sjkh int 15739Sjkh#if has_prototypes 157411894Speterchnamemod( 157511894Speter FILE **fromp, char const *from, char const *to, 157611894Speter int set_mode, mode_t mode, time_t mtime 157711894Speter) 15789Sjkh /* The `#if has_prototypes' is needed because mode_t might promote to int. */ 15799Sjkh#else 158011894Speter chnamemod(fromp, from, to, set_mode, mode, mtime) 158111894Speter FILE **fromp; char const *from,*to; 158211894Speter int set_mode; mode_t mode; time_t mtime; 15839Sjkh#endif 15849Sjkh/* 158511894Speter * Rename a file (with stream pointer *FROMP) from FROM to TO. 15869Sjkh * FROM already exists. 158711894Speter * If 0 < SET_MODE, change the mode to MODE, before renaming if possible. 158811894Speter * If MTIME is not -1, change its mtime to MTIME before renaming. 158911894Speter * Close and clear *FROMP before renaming it. 15909Sjkh * Unlink TO if it already exists. 15919Sjkh * Return -1 on error (setting errno), 0 otherwise. 15929Sjkh */ 15939Sjkh{ 159411894Speter mode_t mode_while_renaming = mode; 159511894Speter int fchmod_set_mode = 0; 159611894Speter 159711894Speter# if bad_a_rename || bad_NFS_rename 159811894Speter struct stat st; 159911894Speter if (bad_NFS_rename || (bad_a_rename && set_mode <= 0)) { 160011894Speter if (fstat(fileno(*fromp), &st) != 0) 160111894Speter return -1; 160211894Speter if (bad_a_rename && set_mode <= 0) 160311894Speter mode = st.st_mode; 160411894Speter } 160511894Speter# endif 160611894Speter 16079Sjkh# if bad_a_rename 16089Sjkh /* 160911894Speter * There's a short window of inconsistency 161011894Speter * during which the lock file is writable. 161111894Speter */ 161211894Speter mode_while_renaming = mode|S_IWUSR; 161311894Speter if (mode != mode_while_renaming) 161411894Speter set_mode = 1; 16159Sjkh# endif 161611894Speter 16179Sjkh# if has_fchmod 161811894Speter if (0<set_mode && fchmod(fileno(*fromp),mode_while_renaming) == 0) 161911894Speter fchmod_set_mode = set_mode; 16209Sjkh# endif 162111894Speter /* If bad_chmod_close, we must close before chmod. */ 162211894Speter Ozclose(fromp); 162311894Speter if (fchmod_set_mode<set_mode && chmod(from, mode_while_renaming) != 0) 162411894Speter return -1; 162511894Speter 162611894Speter if (setmtime(from, mtime) != 0) 16279Sjkh return -1; 16289Sjkh 16299Sjkh# if !has_rename || bad_b_rename 16309Sjkh /* 163111894Speter * There's a short window of inconsistency 163211894Speter * during which TO does not exist. 163311894Speter */ 163411894Speter if (un_link(to) != 0 && errno != ENOENT) 163511894Speter return -1; 16369Sjkh# endif 16379Sjkh 163811894Speter# if has_rename 163911894Speter if (rename(from,to) != 0 && !(has_NFS && errno==ENOENT)) 164011894Speter return -1; 164111894Speter# else 164211894Speter if (do_link(from,to) != 0 || un_link(from) != 0) 164311894Speter return -1; 164411894Speter# endif 16459Sjkh 164611894Speter# if bad_NFS_rename 164711894Speter { 164811894Speter /* 164911894Speter * Check whether the rename falsely reported success. 165011894Speter * A race condition can occur between the rename and the stat. 165111894Speter */ 165211894Speter struct stat tostat; 165311894Speter if (stat(to, &tostat) != 0) 165411894Speter return -1; 165511894Speter if (! same_file(st, tostat, 0)) { 165611894Speter errno = EIO; 165711894Speter return -1; 165811894Speter } 165911894Speter } 166011894Speter# endif 166111894Speter 166211894Speter# if bad_a_rename 166311894Speter if (0 < set_mode && chmod(to, mode) != 0) 166411894Speter return -1; 166511894Speter# endif 166611894Speter 166711894Speter return 0; 16689Sjkh} 16699Sjkh 167011894Speter int 167111894Spetersetmtime(file, mtime) 167211894Speter char const *file; 167311894Speter time_t mtime; 167411894Speter/* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1. */ 167511894Speter{ 167611894Speter static struct utimbuf amtime; /* static so unused fields are zero */ 167711894Speter if (mtime == -1) 167811894Speter return 0; 167911894Speter amtime.actime = now(); 168011894Speter amtime.modtime = mtime; 168111894Speter return utime(file, &amtime); 168211894Speter} 16839Sjkh 16849Sjkh 168511894Speter 16869Sjkh int 16879Sjkhfindlock(delete, target) 16889Sjkh int delete; 16899Sjkh struct hshentry **target; 16909Sjkh/* 16919Sjkh * Find the first lock held by caller and return a pointer 16929Sjkh * to the locked delta; also removes the lock if DELETE. 16939Sjkh * If one lock, put it into *TARGET. 16949Sjkh * Return 0 for no locks, 1 for one, 2 for two or more. 16959Sjkh */ 16969Sjkh{ 169711894Speter register struct rcslock *next, **trail, **found; 16989Sjkh 16999Sjkh found = 0; 17009Sjkh for (trail = &Locks; (next = *trail); trail = &next->nextlock) 17019Sjkh if (strcmp(getcaller(), next->login) == 0) { 17029Sjkh if (found) { 170311894Speter rcserror("multiple revisions locked by %s; please specify one", getcaller()); 17049Sjkh return 2; 17059Sjkh } 17069Sjkh found = trail; 17079Sjkh } 17089Sjkh if (!found) 17099Sjkh return 0; 17109Sjkh next = *found; 17119Sjkh *target = next->delta; 17129Sjkh if (delete) { 171311894Speter next->delta->lockedby = 0; 17149Sjkh *found = next->nextlock; 17159Sjkh } 17169Sjkh return 1; 17179Sjkh} 17189Sjkh 17199Sjkh int 172011894Speteraddlock(delta, verbose) 17219Sjkh struct hshentry * delta; 172211894Speter int verbose; 17239Sjkh/* 17249Sjkh * Add a lock held by caller to DELTA and yield 1 if successful. 172511894Speter * Print an error message if verbose and yield -1 if no lock is added because 17269Sjkh * DELTA is locked by somebody other than caller. 17279Sjkh * Return 0 if the caller already holds the lock. 17289Sjkh */ 17299Sjkh{ 173011894Speter register struct rcslock *next; 17319Sjkh 17329Sjkh for (next = Locks; next; next = next->nextlock) 17339Sjkh if (cmpnum(delta->num, next->delta->num) == 0) 17349Sjkh if (strcmp(getcaller(), next->login) == 0) 17359Sjkh return 0; 17369Sjkh else { 173711894Speter if (verbose) 173811894Speter rcserror("Revision %s is already locked by %s.", 173911894Speter delta->num, next->login 174011894Speter ); 17419Sjkh return -1; 17429Sjkh } 174311894Speter next = ftalloc(struct rcslock); 17449Sjkh delta->lockedby = next->login = getcaller(); 17459Sjkh next->delta = delta; 17469Sjkh next->nextlock = Locks; 17479Sjkh Locks = next; 17489Sjkh return 1; 17499Sjkh} 17509Sjkh 17519Sjkh 17529Sjkh int 17539Sjkhaddsymbol(num, name, rebind) 17549Sjkh char const *num, *name; 17559Sjkh int rebind; 17569Sjkh/* 17579Sjkh * Associate with revision NUM the new symbolic NAME. 17589Sjkh * If NAME already exists and REBIND is set, associate NAME with NUM; 17599Sjkh * otherwise, print an error message and return false; 176011894Speter * Return -1 if unsuccessful, 0 if no change, 1 if change. 17619Sjkh */ 17629Sjkh{ 17639Sjkh register struct assoc *next; 17649Sjkh 17659Sjkh for (next = Symbols; next; next = next->nextassoc) 17669Sjkh if (strcmp(name, next->symbol) == 0) 176711894Speter if (strcmp(next->num,num) == 0) 176811894Speter return 0; 176911894Speter else if (rebind) { 17709Sjkh next->num = num; 177111894Speter return 1; 17729Sjkh } else { 177311894Speter rcserror("symbolic name %s already bound to %s", 17749Sjkh name, next->num 17759Sjkh ); 177611894Speter return -1; 17779Sjkh } 17789Sjkh next = ftalloc(struct assoc); 17799Sjkh next->symbol = name; 17809Sjkh next->num = num; 17819Sjkh next->nextassoc = Symbols; 17829Sjkh Symbols = next; 178311894Speter return 1; 17849Sjkh} 17859Sjkh 17869Sjkh 17879Sjkh 17889Sjkh char const * 17899Sjkhgetcaller() 17909Sjkh/* Get the caller's login name. */ 17919Sjkh{ 17929Sjkh# if has_setuid 17939Sjkh return getusername(euid()!=ruid()); 17949Sjkh# else 17959Sjkh return getusername(false); 17969Sjkh# endif 17979Sjkh} 17989Sjkh 17999Sjkh 18009Sjkh int 18019Sjkhcheckaccesslist() 18029Sjkh/* 18039Sjkh * Return true if caller is the superuser, the owner of the 18049Sjkh * file, the access list is empty, or caller is on the access list. 18059Sjkh * Otherwise, print an error message and return false. 18069Sjkh */ 18079Sjkh{ 18089Sjkh register struct access const *next; 18099Sjkh 18109Sjkh if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0) 18119Sjkh return true; 18129Sjkh 18139Sjkh next = AccessList; 18149Sjkh do { 18159Sjkh if (strcmp(getcaller(), next->login) == 0) 18169Sjkh return true; 18179Sjkh } while ((next = next->nextaccess)); 18189Sjkh 181911894Speter rcserror("user %s not on the access list", getcaller()); 18209Sjkh return false; 18219Sjkh} 18229Sjkh 18239Sjkh 18249Sjkh int 18259Sjkhdorewrite(lockflag, changed) 18269Sjkh int lockflag, changed; 18279Sjkh/* 18289Sjkh * Do nothing if LOCKFLAG is zero. 18299Sjkh * Prepare to rewrite an RCS file if CHANGED is positive. 18309Sjkh * Stop rewriting if CHANGED is zero, because there won't be any changes. 18319Sjkh * Fail if CHANGED is negative. 183211894Speter * Return 0 on success, -1 on failure. 18339Sjkh */ 18349Sjkh{ 183511894Speter int r = 0, e; 18369Sjkh 18379Sjkh if (lockflag) 18389Sjkh if (changed) { 18399Sjkh if (changed < 0) 184011894Speter return -1; 184111894Speter putadmin(); 18429Sjkh puttree(Head, frewrite); 18439Sjkh aprintf(frewrite, "\n\n%s%c", Kdesc, nextc); 18449Sjkh foutptr = frewrite; 18459Sjkh } else { 184611894Speter# if bad_creat0 184711894Speter int nr = !!frewrite, ne = 0; 184811894Speter# endif 184911894Speter ORCSclose(); 18509Sjkh seteid(); 18519Sjkh ignoreints(); 185211894Speter# if bad_creat0 185311894Speter if (nr) { 185411894Speter nr = un_link(newRCSname); 185511894Speter ne = errno; 185611894Speter keepdirtemp(newRCSname); 185711894Speter } 185811894Speter# endif 185911894Speter r = un_link(lockname); 18609Sjkh e = errno; 186111894Speter keepdirtemp(lockname); 18629Sjkh restoreints(); 18639Sjkh setrid(); 186411894Speter if (r != 0) 186511894Speter enerror(e, lockname); 186611894Speter# if bad_creat0 186711894Speter if (nr != 0) { 186811894Speter enerror(ne, newRCSname); 186911894Speter r = -1; 187011894Speter } 187111894Speter# endif 18729Sjkh } 187311894Speter return r; 18749Sjkh} 18759Sjkh 18769Sjkh int 187711894Speterdonerewrite(changed, newRCStime) 18789Sjkh int changed; 187911894Speter time_t newRCStime; 18809Sjkh/* 18819Sjkh * Finish rewriting an RCS file if CHANGED is nonzero. 188211894Speter * Set its mode if CHANGED is positive. 188311894Speter * Set its modification time to NEWRCSTIME unless it is -1. 188411894Speter * Return 0 on success, -1 on failure. 18859Sjkh */ 18869Sjkh{ 188711894Speter int r = 0, e = 0; 188811894Speter# if bad_creat0 188911894Speter int lr, le; 189011894Speter# endif 18919Sjkh 18929Sjkh if (changed && !nerror) { 18939Sjkh if (finptr) { 18949Sjkh fastcopy(finptr, frewrite); 18959Sjkh Izclose(&finptr); 18969Sjkh } 18979Sjkh if (1 < RCSstat.st_nlink) 189811894Speter rcswarn("breaking hard link"); 189911894Speter aflush(frewrite); 19009Sjkh seteid(); 19019Sjkh ignoreints(); 190211894Speter r = chnamemod( 190311894Speter &frewrite, newRCSname, RCSname, changed, 190411894Speter RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH), 190511894Speter newRCStime 19069Sjkh ); 19079Sjkh e = errno; 190811894Speter keepdirtemp(newRCSname); 190911894Speter# if bad_creat0 191011894Speter lr = un_link(lockname); 191111894Speter le = errno; 191211894Speter keepdirtemp(lockname); 191311894Speter# endif 19149Sjkh restoreints(); 19159Sjkh setrid(); 19169Sjkh if (r != 0) { 191711894Speter enerror(e, RCSname); 191811894Speter error("saved in %s", newRCSname); 19199Sjkh } 192011894Speter# if bad_creat0 192111894Speter if (lr != 0) { 192211894Speter enerror(le, lockname); 192311894Speter r = -1; 192411894Speter } 192511894Speter# endif 19269Sjkh } 192711894Speter return r; 19289Sjkh} 19299Sjkh 19309Sjkh void 193111894SpeterORCSclose() 19329Sjkh{ 193311894Speter if (0 <= fdlock) { 193411894Speter if (close(fdlock) != 0) 193511894Speter efaterror(lockname); 193611894Speter fdlock = -1; 193711894Speter } 193811894Speter Ozclose(&frewrite); 19399Sjkh} 194011894Speter 194111894Speter void 194211894SpeterORCSerror() 194311894Speter/* 194411894Speter* Like ORCSclose, except we are cleaning up after an interrupt or fatal error. 194511894Speter* Do not report errors, since this may loop. This is needed only because 194611894Speter* some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and 194711894Speter* some nearly-Posix hosts (e.g. NFS) work better if the files are closed first. 194811894Speter* This isn't a completely reliable away to work around brain-damaged hosts, 194911894Speter* because of the gap between actual file opening and setting frewrite etc., 195011894Speter* but it's better than nothing. 195111894Speter*/ 195211894Speter{ 195311894Speter if (0 <= fdlock) 195411894Speter VOID close(fdlock); 195511894Speter if (frewrite) 195611894Speter /* Avoid fclose, since stdio may not be reentrant. */ 195711894Speter VOID close(fileno(frewrite)); 195811894Speter} 1959