diff options
Diffstat (limited to 'gnu/usr.bin/rcs/lib/rcsedit.c')
-rw-r--r-- | gnu/usr.bin/rcs/lib/rcsedit.c | 1656 |
1 files changed, 1656 insertions, 0 deletions
diff --git a/gnu/usr.bin/rcs/lib/rcsedit.c b/gnu/usr.bin/rcs/lib/rcsedit.c new file mode 100644 index 0000000..fab4f62 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcsedit.c @@ -0,0 +1,1656 @@ +/* + * RCS stream editor + */ +/********************************************************************************** + * edits the input file according to a + * script from stdin, generated by diff -n + * performs keyword expansion + ********************************************************************************** + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +RCS is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + +/* $Log: rcsedit.c,v $ + * Revision 5.11 1991/11/03 01:11:44 eggert + * Move the warning about link breaking to where they're actually being broken. + * + * Revision 5.10 1991/10/07 17:32:46 eggert + * Support piece tables even if !has_mmap. Fix rare NFS bugs. + * + * Revision 5.9 1991/09/17 19:07:40 eggert + * SGI readlink() yields ENXIO, not EINVAL, for nonlinks. + * + * Revision 5.8 1991/08/19 03:13:55 eggert + * Add piece tables, NFS bug workarounds. Catch odd filenames. Tune. + * + * Revision 5.7 1991/04/21 11:58:21 eggert + * Fix errno bugs. Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.6 1991/02/25 07:12:40 eggert + * Fix setuid bug. Support new link behavior. Work around broken "w+" fopen. + * + * Revision 5.5 1990/12/30 05:07:35 eggert + * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL). + * + * Revision 5.4 1990/11/01 05:03:40 eggert + * Permit arbitrary data in comment leaders. + * + * Revision 5.3 1990/09/11 02:41:13 eggert + * Tune expandline(). + * + * Revision 5.2 1990/09/04 08:02:21 eggert + * Count RCS lines better. Improve incomplete line handling. + * + * Revision 5.1 1990/08/29 07:13:56 eggert + * Add -kkvl. + * Fix bug when getting revisions to files ending in incomplete lines. + * Fix bug in comment leader expansion. + * + * Revision 5.0 1990/08/22 08:12:47 eggert + * Don't require final newline. + * Don't append "checked in with -k by " to logs, + * so that checking in a program with -k doesn't change it. + * Don't generate trailing white space for empty comment leader. + * Remove compile-time limits; use malloc instead. Add -k, -V. + * Permit dates past 1999/12/31. Make lock and temp files faster and safer. + * Ansify and Posixate. Check diff's output. + * + * Revision 4.8 89/05/01 15:12:35 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.7 88/11/08 13:54:14 narten + * misplaced semicolon caused infinite loop + * + * Revision 4.6 88/08/09 19:12:45 eggert + * Shrink stdio code size; allow cc -R. + * + * Revision 4.5 87/12/18 11:38:46 narten + * Changes from the 43. version. Don't know the significance of the + * first change involving "rewind". Also, additional "lint" cleanup. + * (Guy Harris) + * + * Revision 4.4 87/10/18 10:32:21 narten + * Updating version numbers. Changes relative to version 1.1 actually + * relative to 4.1 + * + * Revision 1.4 87/09/24 13:59:29 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.3 87/09/15 16:39:39 shepler + * added an initializatin of the variables editline and linecorr + * this will be done each time a file is processed. + * (there was an obscure bug where if co was used to retrieve multiple files + * it would dump) + * fix attributed to Roy Morris @FileNet Corp ...!felix!roy + * + * Revision 1.2 87/03/27 14:22:17 jenkins + * Port to suns + * + * Revision 4.1 83/05/12 13:10:30 wft + * Added new markers Id and RCSfile; added locker to Header and Id. + * Overhauled expandline completely() (problem with $01234567890123456789@). + * Moved trymatch() and marker table to rcskeys.c. + * + * Revision 3.7 83/05/12 13:04:39 wft + * Added retry to expandline to resume after failed match which ended in $. + * Fixed truncation problem for $19chars followed by@@. + * Log no longer expands full path of RCS file. + * + * Revision 3.6 83/05/11 16:06:30 wft + * added retry to expandline to resume after failed match which ended in $. + * Fixed truncation problem for $19chars followed by@@. + * + * Revision 3.5 82/12/04 13:20:56 wft + * Added expansion of keyword Locker. + * + * Revision 3.4 82/12/03 12:26:54 wft + * Added line number correction in case editing does not start at the + * beginning of the file. + * Changed keyword expansion to always print a space before closing KDELIM; + * Expansion for Header shortened. + * + * Revision 3.3 82/11/14 14:49:30 wft + * removed Suffix from keyword expansion. Replaced fclose with ffclose. + * keyreplace() gets log message from delta, not from curlogmsg. + * fixed expression overflow in while(c=putc(GETC.... + * checked nil printing. + * + * Revision 3.2 82/10/18 21:13:39 wft + * I added checks for write errors during the co process, and renamed + * expandstring() to xpandstring(). + * + * Revision 3.1 82/10/13 15:52:55 wft + * changed type of result of getc() from char to int. + * made keyword expansion loop in expandline() portable to machines + * without sign-extension. + */ + + +#include "rcsbase.h" + +libId(editId, "$Id: rcsedit.c,v 5.11 1991/11/03 01:11:44 eggert Exp $") + +static void keyreplace P((enum markers,struct hshentry const*,FILE*)); + + +FILE *fcopy; /* result file descriptor */ +char const *resultfile; /* result file name */ +int locker_expansion; /* should the locker name be appended to Id val? */ +#if !large_memory + static RILE *fedit; /* edit file descriptor */ + static char const *editfile; /* edit pathname */ +#endif +static unsigned long editline; /* edit line counter; #lines before cursor */ +static long linecorr; /* #adds - #deletes in each edit run. */ + /*used to correct editline in case file is not rewound after */ + /* applying one delta */ + +#define DIRTEMPNAMES 2 +enum maker {notmade, real, effective}; +struct buf dirtfname[DIRTEMPNAMES]; /* unlink these when done */ +static enum maker volatile dirtfmaker[DIRTEMPNAMES]; /* if these are set */ + + +#if has_NFS || bad_unlink + int +un_link(s) + char const *s; +/* + * Remove S, even if it is unwritable. + * Ignore unlink() ENOENT failures; NFS generates bogus ones. + */ +{ +# if bad_unlink + int e; + if (unlink(s) == 0) + return 0; + e = errno; +# if has_NFS + if (e == ENOENT) + return 0; +# endif + if (chmod(s, S_IWUSR) != 0) { + errno = e; + return -1; + } +# endif +# if has_NFS + return unlink(s)==0 || errno==ENOENT ? 0 : -1; +# else + return unlink(s); +# endif +} +#endif + +#if !has_rename +# if !has_NFS +# define do_link(s,t) link(s,t) +# else + static int +do_link(s, t) + char const *s, *t; +/* Link S to T, ignoring bogus EEXIST problems due to NFS failures. */ +{ + struct stat sb, tb; + + if (link(s,t) == 0) + return 0; + if (errno != EEXIST) + return -1; + if ( + stat(s, &sb) == 0 && + stat(t, &tb) == 0 && + sb.st_ino == tb.st_ino && + sb.st_dev == tb.st_dev + ) + return 0; + errno = EEXIST; + return -1; +} +# endif +#endif + + + static exiting void +editEndsPrematurely() +{ + fatserror("edit script ends prematurely"); +} + + static exiting void +editLineNumberOverflow() +{ + fatserror("edit script refers to line past end of file"); +} + + +#if large_memory + +#if has_memmove +# define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type)) +#else + static void +movelines(s1, s2, n) + register Iptr_type *s1; + register Iptr_type const *s2; + register unsigned long n; +{ + if (s1 < s2) + do { + *s1++ = *s2++; + } while (--n); + else { + s1 += n; + s2 += n; + do { + *--s1 = *--s2; + } while (--n); + } +} +#endif + +/* + * `line' contains pointers to the lines in the currently `edited' file. + * It is a 0-origin array that represents linelim-gapsize lines. + * line[0..gap-1] and line[gap+gapsize..linelim-1] contain pointers to lines. + * line[gap..gap+gapsize-1] contains garbage. + * + * Any @s in lines are duplicated. + * Lines are terminated by \n, or (for a last partial line only) by single @. + */ +static Iptr_type *line; +static unsigned long gap, gapsize, linelim; + + + static void +insertline(n, l) + unsigned long n; + Iptr_type l; +/* Before line N, insert line L. N is 0-origin. */ +{ + if (linelim-gapsize < n) + editLineNumberOverflow(); + if (!gapsize) + line = + !linelim ? + tnalloc(Iptr_type, linelim = gapsize = 1024) + : ( + gap = gapsize = linelim, + trealloc(Iptr_type, line, linelim <<= 1) + ); + if (n < gap) + movelines(line+n+gapsize, line+n, gap-n); + else if (gap < n) + movelines(line+gap, line+gap+gapsize, n-gap); + + line[n] = l; + gap = n + 1; + gapsize--; +} + + static void +deletelines(n, nlines) + unsigned long n, nlines; +/* Delete lines N through N+NLINES-1. N is 0-origin. */ +{ + unsigned long l = n + nlines; + if (linelim-gapsize < l || l < n) + editLineNumberOverflow(); + if (l < gap) + movelines(line+l+gapsize, line+l, gap-l); + else if (gap < n) + movelines(line+gap, line+gap+gapsize, n-gap); + + gap = n; + gapsize += nlines; +} + + static void +snapshotline(f, l) + register FILE *f; + register Iptr_type l; +{ + register int c; + do { + if ((c = *l++) == SDELIM && *l++ != SDELIM) + return; + aputc(c, f); + } while (c != '\n'); +} + + void +snapshotedit(f) + FILE *f; +/* Copy the current state of the edits to F. */ +{ + register Iptr_type *p, *lim, *l=line; + for (p=l, lim=l+gap; p<lim; ) + snapshotline(f, *p++); + for (p+=gapsize, lim=l+linelim; p<lim; ) + snapshotline(f, *p++); +} + + static void +finisheditline(fin, fout, l, delta) + RILE *fin; + FILE *fout; + Iptr_type l; + struct hshentry const *delta; +{ + Iseek(fin, l); + if (expandline(fin, fout, delta, true, (FILE*)0) < 0) + faterror("finisheditline internal error"); +} + + void +finishedit(delta, outfile, done) + struct hshentry const *delta; + FILE *outfile; + int done; +/* + * Doing expansion if DELTA is set, output the state of the edits to OUTFILE. + * But do nothing unless DONE is set (which means we are on the last pass). + */ +{ + if (done) { + openfcopy(outfile); + outfile = fcopy; + if (!delta) + snapshotedit(outfile); + else { + register Iptr_type *p, *lim, *l = line; + register RILE *fin = finptr; + Iptr_type here = Itell(fin); + for (p=l, lim=l+gap; p<lim; ) + finisheditline(fin, outfile, *p++, delta); + for (p+=gapsize, lim=l+linelim; p<lim; ) + finisheditline(fin, outfile, *p++, delta); + Iseek(fin, here); + } + } +} + +/* Open a temporary FILENAME for output, truncating any previous contents. */ +# define fopen_update_truncate(filename) fopen(filename, FOPEN_W_WORK) +#else /* !large_memory */ + static FILE * +fopen_update_truncate(filename) + char const *filename; +{ +# if bad_fopen_wplus + if (un_link(filename) != 0) + efaterror(filename); +# endif + return fopen(filename, FOPEN_WPLUS_WORK); +} +#endif + + + void +openfcopy(f) + FILE *f; +{ + if (!(fcopy = f)) { + if (!resultfile) + resultfile = maketemp(2); + if (!(fcopy = fopen_update_truncate(resultfile))) + efaterror(resultfile); + } +} + + +#if !large_memory + + static void +swapeditfiles(outfile) + FILE *outfile; +/* Function: swaps resultfile and editfile, assigns fedit=fcopy, + * and rewinds fedit for reading. Set fcopy to outfile if nonnull; + * otherwise, set fcopy to be resultfile opened for reading and writing. + */ +{ + char const *tmpptr; + + editline = 0; linecorr = 0; + if (fseek(fcopy, 0L, SEEK_SET) != 0) + Oerror(); + fedit = fcopy; + tmpptr=editfile; editfile=resultfile; resultfile=tmpptr; + openfcopy(outfile); +} + + void +snapshotedit(f) + FILE *f; +/* Copy the current state of the edits to F. */ +{ + finishedit((struct hshentry *)nil, (FILE*)0, false); + fastcopy(fedit, f); + Irewind(fedit); +} + + void +finishedit(delta, outfile, done) + struct hshentry const *delta; + FILE *outfile; + int done; +/* copy the rest of the edit file and close it (if it exists). + * if delta!=nil, perform keyword substitution at the same time. + * If DONE is set, we are finishing the last pass. + */ +{ + register RILE *fe; + register FILE *fc; + + fe = fedit; + if (fe) { + fc = fcopy; + if (delta!=nil) { + while (1 < expandline(fe,fc,delta,false,(FILE*)0)) + ; + } else { + fastcopy(fe,fc); + } + Ifclose(fe); + } + if (!done) + swapeditfiles(outfile); +} +#endif + + + +#if large_memory +# define copylines(upto,delta) (editline = (upto)) +#else + static void +copylines(upto,delta) + register unsigned long upto; + struct hshentry const *delta; +/* + * Copy input lines editline+1..upto from fedit to fcopy. + * If delta != nil, keyword expansion is done simultaneously. + * editline is updated. Rewinds a file only if necessary. + */ +{ + register int c; + declarecache; + register FILE *fc; + register RILE *fe; + + if (upto < editline) { + /* swap files */ + finishedit((struct hshentry *)nil, (FILE*)0, false); + /* assumes edit only during last pass, from the beginning*/ + } + fe = fedit; + fc = fcopy; + if (editline < upto) + if (delta) + do { + if (expandline(fe,fc,delta,false,(FILE*)0) <= 1) + editLineNumberOverflow(); + } while (++editline < upto); + else { + setupcache(fe); cache(fe); + do { + do { + cachegeteof(c, editLineNumberOverflow();); + aputc(c, fc); + } while (c != '\n'); + } while (++editline < upto); + uncache(fe); + } +} +#endif + + + + void +xpandstring(delta) + struct hshentry const *delta; +/* Function: Reads a string terminated by SDELIM from finptr and writes it + * to fcopy. Double SDELIM is replaced with single SDELIM. + * Keyword expansion is performed with data from delta. + * If foutptr is nonnull, the string is also copied unchanged to foutptr. + */ +{ + while (1 < expandline(finptr,fcopy,delta,true,foutptr)) + ; +} + + + void +copystring() +/* Function: copies a string terminated with a single SDELIM from finptr to + * fcopy, replacing all double SDELIM with a single SDELIM. + * If foutptr is nonnull, the string also copied unchanged to foutptr. + * editline is incremented by the number of lines copied. + * Assumption: next character read is first string character. + */ +{ register c; + declarecache; + register FILE *frew, *fcop; + register int amidline; + register RILE *fin; + + fin = finptr; + setupcache(fin); cache(fin); + frew = foutptr; + fcop = fcopy; + amidline = false; + for (;;) { + GETC(frew,c); + switch (c) { + case '\n': + ++editline; + ++rcsline; + amidline = false; + break; + case SDELIM: + GETC(frew,c); + if (c != SDELIM) { + /* end of string */ + nextc = c; + editline += amidline; + uncache(fin); + return; + } + /* fall into */ + default: + amidline = true; + break; + } + aputc(c,fcop); + } +} + + + void +enterstring() +/* Like copystring, except the string is put into the edit data structure. */ +{ +#if !large_memory + editfile = 0; + fedit = 0; + editline = linecorr = 0; + resultfile = maketemp(1); + if (!(fcopy = fopen_update_truncate(resultfile))) + efaterror(resultfile); + copystring(); +#else + register int c; + declarecache; + register FILE *frew; + register unsigned long e, oe; + register int amidline, oamidline; + register Iptr_type optr; + register RILE *fin; + + e = 0; + gap = 0; + gapsize = linelim; + fin = finptr; + setupcache(fin); cache(fin); + advise_access(fin, MADV_NORMAL); + frew = foutptr; + amidline = false; + for (;;) { + optr = cachetell(); + GETC(frew,c); + oamidline = amidline; + oe = e; + switch (c) { + case '\n': + ++e; + ++rcsline; + amidline = false; + break; + case SDELIM: + GETC(frew,c); + if (c != SDELIM) { + /* end of string */ + nextc = c; + editline = e + amidline; + linecorr = 0; + uncache(fin); + return; + } + /* fall into */ + default: + amidline = true; + break; + } + if (!oamidline) + insertline(oe, optr); + } +#endif +} + + + + + void +#if large_memory +edit_string() +#else + editstring(delta) + struct hshentry const *delta; +#endif +/* + * Read an edit script from finptr and applies it to the edit file. +#if !large_memory + * The result is written to fcopy. + * If delta!=nil, keyword expansion is performed simultaneously. + * If running out of lines in fedit, fedit and fcopy are swapped. + * editfile is the name of the file that goes with fedit. +#endif + * If foutptr is set, the edit script is also copied verbatim to foutptr. + * Assumes that all these files are open. + * resultfile is the name of the file that goes with fcopy. + * Assumes the next input character from finptr is the first character of + * the edit script. Resets nextc on exit. + */ +{ + int ed; /* editor command */ + register int c; + declarecache; + register FILE *frew; +# if !large_memory + register FILE *f; + unsigned long line_lim = ULONG_MAX; + register RILE *fe; +# endif + register unsigned long i; + register RILE *fin; +# if large_memory + register unsigned long j; +# endif + struct diffcmd dc; + + editline += linecorr; linecorr=0; /*correct line number*/ + frew = foutptr; + fin = finptr; + setupcache(fin); + initdiffcmd(&dc); + while (0 <= (ed = getdiffcmd(fin,true,frew,&dc))) +#if !large_memory + if (line_lim <= dc.line1) + editLineNumberOverflow(); + else +#endif + if (!ed) { + copylines(dc.line1-1, delta); + /* skip over unwanted lines */ + i = dc.nlines; + linecorr -= i; + editline += i; +# if large_memory + deletelines(editline+linecorr, i); +# else + fe = fedit; + do { + /*skip next line*/ + do { + Igeteof(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } ); + } while (c != '\n'); + } while (--i); +# endif + } else { + copylines(dc.line1, delta); /*copy only; no delete*/ + i = dc.nlines; +# if large_memory + j = editline+linecorr; +# endif + linecorr += i; +#if !large_memory + f = fcopy; + if (delta) + do { + switch (expandline(fin,f,delta,true,frew)) { + case 0: case 1: + if (i==1) + return; + /* fall into */ + case -1: + editEndsPrematurely(); + } + } while (--i); + else +#endif + { + cache(fin); + do { +# if large_memory + insertline(j++, cachetell()); +# endif + for (;;) { + GETC(frew, c); +# if !large_memory + aputc(c, f); +# endif + if (c == '\n') + break; + if (c==SDELIM) { + GETC(frew, c); + if (c!=SDELIM) { + if (--i) + editEndsPrematurely(); + nextc = c; + uncache(fin); + return; + } + } + } + ++rcsline; + } while (--i); + uncache(fin); + } + } +} + + + +/* The rest is for keyword expansion */ + + + + int +expandline(infile, outfile, delta, delimstuffed, frewfile) + RILE *infile; + FILE *outfile, *frewfile; + struct hshentry const *delta; + int delimstuffed; +/* + * Read a line from INFILE and write it to OUTFILE. + * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM. + * Keyword expansion is performed with data from delta. + * If FREWFILE is set, copy the line unchanged to FREWFILE. + * DELIMSTUFFED must be true if FREWFILE is set. + * Yields -1 if no data is copied, 0 if an incomplete line is copied, + * 2 if a complete line is copied; adds 1 to yield if expansion occurred. + */ +{ + register c; + declarecache; + register FILE *out, *frew; + register char * tp; + register int e, ds, r; + char const *tlim; + static struct buf keyval; + enum markers matchresult; + + setupcache(infile); cache(infile); + out = outfile; + frew = frewfile; + ds = delimstuffed; + bufalloc(&keyval, keylength+3); + e = 0; + r = -1; + + for (;;) { + if (ds) { + GETC(frew, c); + } else + cachegeteof(c, goto uncache_exit;); + for (;;) { + switch (c) { + case SDELIM: + if (ds) { + GETC(frew, c); + if (c != SDELIM) { + /* end of string */ + nextc=c; + goto uncache_exit; + } + } + /* fall into */ + default: + aputc(c,out); + r = 0; + break; + + case '\n': + rcsline += ds; + aputc(c,out); + r = 2; + goto uncache_exit; + + case KDELIM: + r = 0; + /* check for keyword */ + /* first, copy a long enough string into keystring */ + tp = keyval.string; + *tp++ = KDELIM; + for (;;) { + if (ds) { + GETC(frew, c); + } else + cachegeteof(c, goto keystring_eof;); + if (tp < keyval.string+keylength+1) + switch (ctab[c]) { + case LETTER: case Letter: + *tp++ = c; + continue; + default: + break; + } + break; + } + *tp++ = c; *tp = '\0'; + matchresult = trymatch(keyval.string+1); + if (matchresult==Nomatch) { + tp[-1] = 0; + aputs(keyval.string, out); + continue; /* last c handled properly */ + } + + /* Now we have a keyword terminated with a K/VDELIM */ + if (c==VDELIM) { + /* try to find closing KDELIM, and replace value */ + tlim = keyval.string + keyval.size; + for (;;) { + if (ds) { + GETC(frew, c); + } else + cachegeteof(c, goto keystring_eof;); + if (c=='\n' || c==KDELIM) + break; + *tp++ =c; + if (tlim <= tp) + tp = bufenlarge(&keyval, &tlim); + if (c==SDELIM && ds) { /*skip next SDELIM */ + GETC(frew, c); + if (c != SDELIM) { + /* end of string before closing KDELIM or newline */ + nextc = c; + goto keystring_eof; + } + } + } + if (c!=KDELIM) { + /* couldn't find closing KDELIM -- give up */ + *tp = 0; + aputs(keyval.string, out); + continue; /* last c handled properly */ + } + } + /* now put out the new keyword value */ + keyreplace(matchresult,delta,out); + e = 1; + break; + } + break; + } + } + + keystring_eof: + *tp = 0; + aputs(keyval.string, out); + uncache_exit: + uncache(infile); + return r + e; +} + + +char const ciklog[ciklogsize] = "checked in with -k by "; + + static void +keyreplace(marker,delta,out) + enum markers marker; + register struct hshentry const *delta; + register FILE *out; +/* function: outputs the keyword value(s) corresponding to marker. + * Attributes are derived from delta. + */ +{ + register char const *sp, *cp, *date; + register char c; + register size_t cs, cw, ls; + char const *sp1; + char datebuf[datesize]; + int RCSv; + + sp = Keyword[(int)marker]; + + if (Expand == KEY_EXPAND) { + aprintf(out, "%c%s%c", KDELIM, sp, KDELIM); + return; + } + + date= delta->date; + RCSv = RCSversion; + + if (Expand == KEYVAL_EXPAND || Expand == KEYVALLOCK_EXPAND) + aprintf(out, "%c%s%c%c", KDELIM, sp, VDELIM, + marker==Log && RCSv<VERSION(5) ? '\t' : ' ' + ); + + switch (marker) { + case Author: + aputs(delta->author, out); + break; + case Date: + aputs(date2str(date,datebuf), out); + break; + case Id: + case Header: + aprintf(out, "%s %s %s %s %s", + marker==Id || RCSv<VERSION(4) + ? basename(RCSfilename) + : getfullRCSname(), + delta->num, + date2str(date, datebuf), + delta->author, + RCSv==VERSION(3) && delta->lockedby ? "Locked" + : delta->state + ); + if (delta->lockedby!=nil) + if (VERSION(5) <= RCSv) { + if (locker_expansion || Expand==KEYVALLOCK_EXPAND) + aprintf(out, " %s", delta->lockedby); + } else if (RCSv == VERSION(4)) + aprintf(out, " Locker: %s", delta->lockedby); + break; + case Locker: + if (delta->lockedby) + if ( + locker_expansion + || Expand == KEYVALLOCK_EXPAND + || RCSv <= VERSION(4) + ) + aputs(delta->lockedby, out); + break; + case Log: + case RCSfile: + aputs(basename(RCSfilename), out); + break; + case Revision: + aputs(delta->num, out); + break; + case Source: + aputs(getfullRCSname(), out); + break; + case State: + aputs(delta->state, out); + break; + default: + break; + } + if (Expand == KEYVAL_EXPAND || Expand == KEYVALLOCK_EXPAND) { + afputc(' ', out); + afputc(KDELIM, out); + } + if (marker == Log) { + sp = delta->log.string; + ls = delta->log.size; + if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1)) + return; + afputc('\n', out); + cp = Comment.string; + cw = cs = Comment.size; + awrite(cp, cs, out); + /* oddity: 2 spaces between date and time, not 1 as usual */ + sp1 = strchr(date2str(date,datebuf), ' '); + aprintf(out, "Revision %s %.*s %s %s", + delta->num, (int)(sp1-datebuf), datebuf, sp1, delta->author + ); + /* Do not include state: it may change and is not updated. */ + /* Comment is the comment leader. */ + if (VERSION(5) <= RCSv) + for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw) + ; + for (;;) { + afputc('\n', out); + awrite(cp, cw, out); + if (!ls) + break; + --ls; + c = *sp++; + if (c != '\n') { + awrite(cp+cw, cs-cw, out); + do { + afputc(c,out); + if (!ls) + break; + --ls; + c = *sp++; + } while (c != '\n'); + } + } + } +} + +#if has_readlink + static int +resolve_symlink(L) + struct buf *L; +/* + * If L is a symbolic link, resolve it to the name that it points to. + * If unsuccessful, set errno and yield -1. + * If it points to an existing file, yield 1. + * Otherwise, set errno=ENOENT and yield 0. + */ +{ + char *b, a[SIZEABLE_PATH]; + int e; + size_t s; + ssize_t r; + struct buf bigbuf; + unsigned linkcount = MAXSYMLINKS + 1; + + b = a; + s = sizeof(a); + bufautobegin(&bigbuf); + while ((r = readlink(L->string,b,s)) != -1) + if (r == s) { + bufalloc(&bigbuf, s<<1); + b = bigbuf.string; + s = bigbuf.size; + } else if (!--linkcount) { + errno = ELOOP; + return -1; + } else { + /* Splice symbolic link into L. */ + b[r] = '\0'; + L->string[ROOTPATH(b) ? (size_t)0 : dirlen(L->string)] = '\0'; + bufscat(L, b); + } + e = errno; + bufautoend(&bigbuf); + errno = e; + switch (e) { + case ENXIO: + case EINVAL: return 1; + case ENOENT: return 0; + default: return -1; + } +} +#endif + + RILE * +rcswriteopen(RCSbuf, status, mustread) + struct buf *RCSbuf; + struct stat *status; + int mustread; +/* + * Create the lock file corresponding to RCSNAME. + * Then try to open RCSNAME for reading and yield its FILE* descriptor. + * Put its status into *STATUS too. + * MUSTREAD is true if the file must already exist, too. + * If all goes well, discard any previously acquired locks, + * and set frewrite to the FILE* descriptor of the lock file, + * which will eventually turn into the new RCS file. + */ +{ + register char *tp; + register char const *sp, *RCSname, *x; + RILE *f; + size_t l; + int e, exists, fdesc, previouslock, r; + struct buf *dirt; + struct stat statbuf; + + previouslock = frewrite != 0; + exists = +# if has_readlink + resolve_symlink(RCSbuf); +# else + stat(RCSbuf->string, &statbuf) == 0 ? 1 + : errno==ENOENT ? 0 : -1; +# endif + if (exists < (mustread|previouslock)) + /* + * There's an unusual problem with the RCS file; + * or the RCS file doesn't exist, + * and we must read or we already have a lock elsewhere. + */ + return 0; + + RCSname = RCSbuf->string; + sp = basename(RCSname); + l = sp - RCSname; + dirt = &dirtfname[previouslock]; + bufscpy(dirt, RCSname); + tp = dirt->string + l; + x = rcssuffix(RCSname); +# if has_readlink + if (!x) { + error("symbolic link to non RCS filename `%s'", RCSname); + errno = EINVAL; + return 0; + } +# endif + if (*sp == *x) { + error("RCS filename `%s' incompatible with suffix `%s'", sp, x); + errno = EINVAL; + return 0; + } + /* Create a lock file whose name is a function of the RCS filename. */ + if (*x) { + /* + * The suffix is nonempty. + * The lock filename is the first char of of the suffix, + * followed by the RCS filename with last char removed. E.g.: + * foo,v RCS filename with suffix ,v + * ,foo, lock filename + */ + *tp++ = *x; + while (*sp) + *tp++ = *sp++; + *--tp = 0; + } else { + /* + * The suffix is empty. + * The lock filename is the RCS filename + * with last char replaced by '_'. + */ + while ((*tp++ = *sp++)) + ; + tp -= 2; + if (*tp == '_') { + error("RCS filename `%s' ends with `%c'", RCSname, *tp); + errno = EINVAL; + return 0; + } + *tp = '_'; + } + + sp = tp = dirt->string; + + f = 0; + + /* + * good news: + * open(f, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY) is atomic + * according to Posix 1003.1-1990. + * bad news: + * NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990. + * good news: + * (O_TRUNC,READONLY) normally guarantees atomicity even with NFS. + * bad news: + * If you're root, (O_TRUNC,READONLY) doesn't guarantee atomicity. + * good news: + * Root-over-the-wire NFS access is rare for security reasons. + * This bug has never been reported in practice with RCS. + * So we don't worry about this bug. + * + * An even rarer NFS bug can occur when clients retry requests. + * Suppose client A renames the lock file ",f," to "f,v" + * at about the same time that client B creates ",f,", + * and suppose A's first rename request is delayed, so A reissues it. + * The sequence of events might be: + * A sends rename(",f,", "f,v") + * B sends create(",f,") + * A sends retry of rename(",f,", "f,v") + * server receives, does, and acknowledges A's first rename() + * A receives acknowledgment, and its RCS program exits + * server receives, does, and acknowledges B's create() + * server receives, does, and acknowledges A's retry of rename() + * This not only wrongly deletes B's lock, it removes the RCS file! + * Most NFS implementations have idempotency caches that usually prevent + * this scenario, but such caches are finite and can be overrun. + * This problem afflicts programs that use the traditional + * Unix method of using link() and unlink() to get and release locks, + * as well as RCS's method of using open() and rename(). + * There is no easy workaround for either link-unlink or open-rename. + * Any new method based on lockf() seemingly would be incompatible with + * the old methods; besides, lockf() is notoriously buggy under NFS. + * Since this problem afflicts scads of Unix programs, but is so rare + * that nobody seems to be worried about it, we won't worry either. + */ +# define READONLY (S_IRUSR|S_IRGRP|S_IROTH) +# if !open_can_creat +# define create(f) creat(f, READONLY) +# else +# define create(f) open(f, O_BINARY|O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY) +# endif + + catchints(); + ignoreints(); + + /* + * Create a lock file for an RCS file. This should be atomic, i.e. + * if two processes try it simultaneously, at most one should succeed. + */ + seteid(); + fdesc = create(sp); + e = errno; + setrid(); + + if (fdesc < 0) { + if (e == EACCES && stat(tp,&statbuf) == 0) + /* The RCS file is busy. */ + e = EEXIST; + } else { + dirtfmaker[0] = effective; + e = ENOENT; + if (exists) { + f = Iopen(RCSname, FOPEN_R, status); + e = errno; + if (f && previouslock) { + /* Discard the previous lock in favor of this one. */ + Ozclose(&frewrite); + seteid(); + if ((r = un_link(newRCSfilename)) != 0) + e = errno; + setrid(); + if (r != 0) + enfaterror(e, newRCSfilename); + bufscpy(&dirtfname[0], tp); + } + } + if (!(frewrite = fdopen(fdesc, FOPEN_W))) { + efaterror(newRCSfilename); + } + } + + restoreints(); + + errno = e; + return f; +} + + void +keepdirtemp(name) + char const *name; +/* Do not unlink name, either because it's not there any more, + * or because it has already been unlinked. + */ +{ + register int i; + for (i=DIRTEMPNAMES; 0<=--i; ) + if (dirtfname[i].string == name) { + dirtfmaker[i] = notmade; + return; + } + faterror("keepdirtemp"); +} + + char const * +makedirtemp(name, n) + register char const *name; + int n; +/* + * Have maketemp() do all the work if name is null. + * Otherwise, create a unique filename in name's dir using n and name + * and store it into the dirtfname[n]. + * Because of storage in tfnames, dirtempunlink() can unlink the file later. + * Return a pointer to the filename created. + */ +{ + register char *tp, *np; + register size_t dl; + register struct buf *bn; + + if (!name) + return maketemp(n); + dl = dirlen(name); + bn = &dirtfname[n]; + bufalloc(bn, +# if has_mktemp + dl + 9 +# else + strlen(name) + 3 +# endif + ); + bufscpy(bn, name); + np = tp = bn->string; + tp += dl; + *tp++ = '_'; + *tp++ = '0'+n; + catchints(); +# if has_mktemp + VOID strcpy(tp, "XXXXXX"); + if (!mktemp(np) || !*np) + faterror("can't make temporary file name `%.*s%c_%cXXXXXX'", + (int)dl, name, SLASH, '0'+n + ); +# else + /* + * Posix 1003.1-1990 has no reliable way + * to create a unique file in a named directory. + * We fudge here. If the working file name is abcde, + * the temp filename is _Ncde where N is a digit. + */ + name += dl; + if (*name) name++; + if (*name) name++; + VOID strcpy(tp, name); +# endif + dirtfmaker[n] = real; + return np; +} + + void +dirtempunlink() +/* Clean up makedirtemp() files. May be invoked by signal handler. */ +{ + register int i; + enum maker m; + + for (i = DIRTEMPNAMES; 0 <= --i; ) + if ((m = dirtfmaker[i]) != notmade) { + if (m == effective) + seteid(); + VOID un_link(dirtfname[i].string); + if (m == effective) + setrid(); + dirtfmaker[i] = notmade; + } +} + + + int +#if has_prototypes +chnamemod(FILE **fromp, char const *from, char const *to, mode_t mode) + /* The `#if has_prototypes' is needed because mode_t might promote to int. */ +#else + chnamemod(fromp,from,to,mode) FILE **fromp; char const *from,*to; mode_t mode; +#endif +/* + * Rename a file (with optional stream pointer *FROMP) from FROM to TO. + * FROM already exists. + * Change its mode to MODE, before renaming if possible. + * If FROMP, close and clear *FROMP before renaming it. + * Unlink TO if it already exists. + * Return -1 on error (setting errno), 0 otherwise. + */ +{ +# if bad_a_rename + /* + * This host is brain damaged. A race condition is possible + * while the lock file is temporarily writable. + * There doesn't seem to be a workaround. + */ + mode_t mode_while_renaming = mode|S_IWUSR; +# else +# define mode_while_renaming mode +# endif + if (fromp) { +# if has_fchmod + if (fchmod(fileno(*fromp), mode_while_renaming) != 0) + return -1; +# endif + Ozclose(fromp); + } +# if has_fchmod + else +# endif + if (chmod(from, mode_while_renaming) != 0) + return -1; + +# if !has_rename || bad_b_rename + VOID un_link(to); + /* + * We need not check the result; + * link() or rename() will catch it. + * No harm is done if TO does not exist. + * However, there's a short window of inconsistency + * during which TO does not exist. + */ +# endif + + return +# if !has_rename + do_link(from,to) != 0 ? -1 : un_link(from) +# else + rename(from, to) != 0 +# if has_NFS + && errno != ENOENT +# endif + ? -1 +# if bad_a_rename + : mode != mode_while_renaming ? chmod(to, mode) +# endif + : 0 +# endif + ; + +# undef mode_while_renaming +} + + + + int +findlock(delete, target) + int delete; + struct hshentry **target; +/* + * Find the first lock held by caller and return a pointer + * to the locked delta; also removes the lock if DELETE. + * If one lock, put it into *TARGET. + * Return 0 for no locks, 1 for one, 2 for two or more. + */ +{ + register struct lock *next, **trail, **found; + + found = 0; + for (trail = &Locks; (next = *trail); trail = &next->nextlock) + if (strcmp(getcaller(), next->login) == 0) { + if (found) { + error("multiple revisions locked by %s; please specify one", getcaller()); + return 2; + } + found = trail; + } + if (!found) + return 0; + next = *found; + *target = next->delta; + if (delete) { + next->delta->lockedby = nil; + *found = next->nextlock; + } + return 1; +} + + int +addlock(delta) + struct hshentry * delta; +/* + * Add a lock held by caller to DELTA and yield 1 if successful. + * Print an error message and yield -1 if no lock is added because + * DELTA is locked by somebody other than caller. + * Return 0 if the caller already holds the lock. + */ +{ + register struct lock *next; + + next=Locks; + for (next = Locks; next; next = next->nextlock) + if (cmpnum(delta->num, next->delta->num) == 0) + if (strcmp(getcaller(), next->login) == 0) + return 0; + else { + error("revision %s already locked by %s", + delta->num, next->login + ); + return -1; + } + next = ftalloc(struct lock); + delta->lockedby = next->login = getcaller(); + next->delta = delta; + next->nextlock = Locks; + Locks = next; + return 1; +} + + + int +addsymbol(num, name, rebind) + char const *num, *name; + int rebind; +/* + * Associate with revision NUM the new symbolic NAME. + * If NAME already exists and REBIND is set, associate NAME with NUM; + * otherwise, print an error message and return false; + * Return true if successful. + */ +{ + register struct assoc *next; + + for (next = Symbols; next; next = next->nextassoc) + if (strcmp(name, next->symbol) == 0) + if (rebind || strcmp(next->num,num) == 0) { + next->num = num; + return true; + } else { + error("symbolic name %s already bound to %s", + name, next->num + ); + return false; + } + next = ftalloc(struct assoc); + next->symbol = name; + next->num = num; + next->nextassoc = Symbols; + Symbols = next; + return true; +} + + + + char const * +getcaller() +/* Get the caller's login name. */ +{ +# if has_setuid + return getusername(euid()!=ruid()); +# else + return getusername(false); +# endif +} + + + int +checkaccesslist() +/* + * Return true if caller is the superuser, the owner of the + * file, the access list is empty, or caller is on the access list. + * Otherwise, print an error message and return false. + */ +{ + register struct access const *next; + + if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0) + return true; + + next = AccessList; + do { + if (strcmp(getcaller(), next->login) == 0) + return true; + } while ((next = next->nextaccess)); + + error("user %s not on the access list", getcaller()); + return false; +} + + + int +dorewrite(lockflag, changed) + int lockflag, changed; +/* + * Do nothing if LOCKFLAG is zero. + * Prepare to rewrite an RCS file if CHANGED is positive. + * Stop rewriting if CHANGED is zero, because there won't be any changes. + * Fail if CHANGED is negative. + * Return true on success. + */ +{ + int r, e; + + if (lockflag) + if (changed) { + if (changed < 0) + return false; + putadmin(frewrite); + puttree(Head, frewrite); + aprintf(frewrite, "\n\n%s%c", Kdesc, nextc); + foutptr = frewrite; + } else { + Ozclose(&frewrite); + seteid(); + ignoreints(); + r = un_link(newRCSfilename); + e = errno; + keepdirtemp(newRCSfilename); + restoreints(); + setrid(); + if (r != 0) { + enerror(e, RCSfilename); + return false; + } + } + return true; +} + + int +donerewrite(changed) + int changed; +/* + * Finish rewriting an RCS file if CHANGED is nonzero. + * Return true on success. + */ +{ + int r, e; + + if (changed && !nerror) { + if (finptr) { + fastcopy(finptr, frewrite); + Izclose(&finptr); + } + if (1 < RCSstat.st_nlink) + warn("breaking hard link to %s", RCSfilename); + seteid(); + ignoreints(); + r = chnamemod(&frewrite, newRCSfilename, RCSfilename, + RCSstat.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH) + ); + e = errno; + keepdirtemp(newRCSfilename); + restoreints(); + setrid(); + if (r != 0) { + enerror(e, RCSfilename); + error("saved in %s", newRCSfilename); + dirtempunlink(); + return false; + } + } + return true; +} + + void +aflush(f) + FILE *f; +{ + if (fflush(f) != 0) + Oerror(); +} |