summaryrefslogtreecommitdiffstats
path: root/gnu/usr.bin/rcs/lib/rcsedit.c
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/usr.bin/rcs/lib/rcsedit.c')
-rw-r--r--gnu/usr.bin/rcs/lib/rcsedit.c1656
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();
+}
OpenPOWER on IntegriCloud