1
0
Files
irix-657m-src/eoe/cmd/rcs/rcsedit.c
2022-09-29 17:59:04 +03:00

2610 lines
66 KiB
C

/* RCS stream editor */
/******************************************************************************
* edits the input file according to a
* script from stdin, generated by diff -n
* performs keyword expansion
******************************************************************************
*/
/* Copyright 1982, 1988, 1989 Walter Tichy
Copyright 1990, 1991, 1992, 1993, 1994, 1995 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,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Report problems and direct all questions to:
rcs-bugs@cs.purdue.edu
*/
/*
* $Log: rcsedit.c,v $
* Revision 1.8 1997/03/31 21:28:10 msf
* Upgrade RCS from 5.6 GNU release to 5.7.
* Include additional hooks for future programmatic (API) interfaces
* (some code is bracketed with #ifdef RCSLIBRARY).
*
* Revision 1.3 1997/03/20 20:45:49 pj
* * Replaced the "bool" type in librcs with "rbool", to avoid conflict
* with the future C++ keyword "bool".
*
* Revision 1.2 1997/01/27 22:04:44 msf
* Add parameter to editstring() so that it can accept a diff script from
* an input file as well as in the form of a string within an archive (,v) file.
* Remove unused "dobreak" parameter in checklock().
* Add support for atomic transactions, including changing chnamemod()
* so that it can work with just a from name (without an open file pointer
* to the from file).
*
* Revision 1.1 1996/10/03 17:34:23 pj
* TAKE ptools now has a copy of kudzu RCS.
* * This take puts a complete copy of kudzu's irix/cmd/rcs in
* the ptools source. Eventually, when ptools is building
* against a kudzu based ROOT, we can remove this copy,
* and use the RCS library that will ship with kudzu.
* But that will be a while -- ptools currently builds with
* an IRIX 5.3 ROOT, in order to provide support for the
* widest practical audience.
* * This is just an exact copy of the kudzu RCS source.
* It doesn't yet build against an IRIX 5.3 ROOT.
* Fixes for that problem will be forthcoming.
*
* Revision 1.7.1.2 1996/06/28 23:39:06 msf
* Changes for RCS library:
* 1) Add implementation of namespaces.
* 2) Move sendmail() from rcs.c to here (to allow access by multiple routines)
* 3) Add checklock() (like breaklock() in rcs.c, but more general; will
* return info on whether caller has lock, and optionally tries to break it)
* 4) rcswriteopen() extended - can now be used to simply create a lockfile
* for an archive file which is already opened for read, rather than
* always opening the file as well.
* 5) chnamemod() extended - may be used to simply change mode on a file,
* without renaming it (if the "to" parameter is NULL)
* 6) donerewrite() no longer closes finptr - this is always handled by
* caller (ci, rcs, library).
*
* Revision 1.7.1.1 1996/06/25 23:34:52 msf
* Create branch for RCS library, based on version 5.7.
*
* Revision 5.19 1995/06/16 06:19:24 eggert
* Update FSF address.
*
* Revision 5.18 1995/06/01 16:23:43 eggert
* (dirtpname): No longer external.
* (do_link): Simplify logic.
* (finisheditline, finishedit): Replace Iseek/Itell with what they stand for.
* (fopen_update_truncate): Replace `#if' with `if'.
* (keyreplace, makedirtemp): dirlen(x) -> basefilename(x)-x.
*
* (edit_string): Fix bug: if !large_memory, a bogus trailing `@' was output
* at the end of incomplete lines.
*
* (keyreplace): Do not assume that seeking backwards
* at the start of a file will fail; on some systems it succeeds.
* Convert C- and Pascal-style comment starts to ` *' in comment leader.
*
* (rcswriteopen): Use fdSafer to get safer file descriptor.
* Open RCS file with FOPEN_RB.
*
* (chnamemod): Work around bad_NFS_rename bug; don't ignore un_link result.
* Fall back on chmod if fchmod fails, since it might be ENOSYS.
*
* (aflush): Move to rcslex.c.
*
* Revision 5.17 1994/03/20 04:52:58 eggert
* Normally calculate the $Log prefix from context, not from RCS file.
* Move setmtime here from rcsutil.c. Add ORCSerror. Remove lint.
*
* Revision 5.16 1993/11/03 17:42:27 eggert
* Add -z. Add Name keyword. If bad_unlink, ignore errno when unlink fails.
* Escape white space, $, and \ in keyword string file names.
* Don't output 2 spaces between date and time after Log.
*
* Revision 5.15 1992/07/28 16:12:44 eggert
* Some hosts have readlink but not ELOOP. Avoid `unsigned'.
* Preserve dates more systematically. Statement macro names now end in _.
*
* Revision 5.14 1992/02/17 23:02:24 eggert
* Add -T support.
*
* Revision 5.13 1992/01/24 18:44:19 eggert
* Add support for bad_chmod_close, bad_creat0.
*
* Revision 5.12 1992/01/06 02:42:34 eggert
* Add setmode parameter to chnamemod. addsymbol now reports changes.
* while (E) ; -> while (E) continue;
*
* 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 1.8 1997/03/31 21:28:10 msf Exp $")
static void editEndsPrematurely P((void)) exiting;
static void editLineNumberOverflow P((void)) exiting;
static void escape_string P((FILE*,char const*));
static void keyreplace P((enum markers,struct hshentry const*,int,RILE*,FILE*,int));
static int lookup1nameval
P((struct namespace*, const char *, const char *, const char **, rbool));
static struct rcsNameVal *get1nameval P((struct namespace*,struct rcsNameVal*));
FILE *fcopy; /* result file descriptor */
char const *resultname; /* result pathname */
int locker_expansion; /* should the locker name be appended to Id val? */
int suppress_mail; /* should mail to lock owner be suppressed? */
char *rcsMailtxt = NULL; /* mail message to use when breaking lock */
int rcsAtomic = 0; /* should all updates be committed atomically? */
#if !large_memory
static RILE *fedit; /* edit file descriptor */
static char const *editname; /* edit pathname */
#endif
static 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 */
/* indexes into dirtpname */
#define lockdirtp_index 0
#define newRCSdirtp_index bad_creat0
#define newworkdirtp_index (newRCSdirtp_index+1)
#define DIRTEMPNAMES (newworkdirtp_index + 1)
enum maker {notmade, real, effective};
static struct buf dirtpname[DIRTEMPNAMES]; /* unlink these when done */
static enum maker volatile dirtpmaker[DIRTEMPNAMES]; /* if these are set */
#define lockname (dirtpname[lockdirtp_index].string)
#define newRCSname (dirtpname[newRCSdirtp_index].string)
/* Entry into list of files to be updated */
struct updateEntry {
struct buf archive; /* name of archive file to be updated */
struct buf lockfile;/* name of lock file (not temp file) */
struct buf tmpfile; /* separate file w/ updated version of archive */
int set_mode; /* flag - is mode of file to be set? */
mode_t mode; /* if so, use this mode */
time_t time; /* mod time of file */
struct updateEntry *next;
};
/* "Object" containing list of files (and supporting data) to commit.
* By walking this list, one may commit all changes atomically.
*/
struct updateObj {
/* public:
* updateObjAdd() - add an entry to the list
* updateObjGetFirst() - get first entry in list
* updateObjGetNext() - get next entry in list
* updateObjClear() - reset object to empty list
*
* updateObjApply() - apply changes recorded in list (higher level op)
* updateObjAbort() - abort changes in list (i.e. unlink assoc'd files)
*/
/* private */
struct updateEntry Head; /* first entry (if any) in list */
struct updateEntry *tailptr; /* last entry used (could be others
* following in list, unreclaimed mem)
* If null, Head is unused.
*/
struct updateEntry *curptr; /* pointer to current returned Entry */
};
static struct updateObj updateObj; /* The actual list of files */
/* The services */
static const struct updateEntry *updateObjGetFirst P((const struct updateObj*));
static const struct updateEntry *updateObjGetNext P((const struct updateObj *));
static int updateObjAdd
P((struct updateObj*,const char*,const char*,const char*,int,mode_t,time_t));
static void updateObjClear P((struct updateObj *));
static int updateObjApply P((struct updateObj *));
static int updateObjAbort P((struct updateObj *));
#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
if (unlink(s) == 0)
return 0;
else {
int e = errno;
/*
* Forge ahead even if errno == ENOENT; some completely
* brain-damaged hosts (e.g. PCTCP 2.2) yield ENOENT
* even for existing unwritable files.
*/
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 P((char const*,char const*));
static int
do_link(s, t)
char const *s, *t;
/* Link S to T, ignoring bogus EEXIST problems due to NFS failures. */
{
int r = link(s, t);
if (r != 0 && errno == EEXIST) {
struct stat sb, tb;
if (
stat(s, &sb) == 0 &&
stat(t, &tb) == 0 &&
same_file(sb, tb, 0)
)
r = 0;
errno = EEXIST;
}
return r;
}
# endif
#endif
static void
editEndsPrematurely()
{
fatserror("edit script ends prematurely");
}
static 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 P((Iptr_type*,Iptr_type const*,long));
static void
movelines(s1, s2, n)
register Iptr_type *s1;
register Iptr_type const *s2;
register long n;
{
if (s1 < s2)
do {
*s1++ = *s2++;
} while (--n);
else {
s1 += n;
s2 += n;
do {
*--s1 = *--s2;
} while (--n);
}
}
#endif
static void deletelines P((long,long));
static void finisheditline P((RILE*,FILE*,Iptr_type,struct hshentry const*));
static void insertline P((long,Iptr_type));
static void snapshotline P((FILE*,Iptr_type));
/*
* `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] hold 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 size_t gap, gapsize, linelim;
static void
insertline(n, l)
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)
long n, nlines;
/* Delete lines N through N+NLINES-1. N is 0-origin. */
{
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;
{
fin->ptr = l;
if (expandline(fin, fout, delta, true, (FILE*)0, true) < 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 = fin->ptr;
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);
fin->ptr = here;
}
}
}
/* Open a temporary NAME for output, truncating any previous contents. */
# define fopen_update_truncate(name) fopenSafer(name, FOPEN_W_WORK)
#else /* !large_memory */
static FILE * fopen_update_truncate P((char const*));
static FILE *
fopen_update_truncate(name)
char const *name;
{
if (bad_fopen_wplus && un_link(name) != 0)
efaterror(name);
return fopenSafer(name, FOPEN_WPLUS_WORK);
}
#endif
void
openfcopy(f)
FILE *f;
{
if (!(fcopy = f)) {
if (!resultname)
resultname = maketemp(2);
if (!(fcopy = fopen_update_truncate(resultname)))
efaterror(resultname);
}
}
#if !large_memory
static void swapeditfiles P((FILE*));
static void
swapeditfiles(outfile)
FILE *outfile;
/* Function: swaps resultname and editname, assigns fedit=fcopy,
* and rewinds fedit for reading. Set fcopy to outfile if nonnull;
* otherwise, set fcopy to be resultname opened for reading and writing.
*/
{
char const *tmpptr;
editline = 0; linecorr = 0;
Orewind(fcopy);
fedit = fcopy;
tmpptr=editname; editname=resultname; resultname=tmpptr;
openfcopy(outfile);
}
void
snapshotedit(f)
FILE *f;
/* Copy the current state of the edits to F. */
{
finishedit((struct hshentry *)0, (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, 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) {
while (1 < expandline(fe,fc,delta,false,(FILE*)0,true))
;
} else {
fastcopy(fe,fc);
}
Ifclose(fe);
}
if (!done)
swapeditfiles(outfile);
}
#endif
#if large_memory
# define copylines(upto,delta) (editline = (upto))
#else
static void copylines P((long,struct hshentry const*));
static void
copylines(upto, delta)
register long upto;
struct hshentry const *delta;
/*
* Copy input lines editline+1..upto from fedit to fcopy.
* If delta, 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 *)0, (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,true) <= 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,true))
continue;
}
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
editname = 0;
fedit = 0;
editline = linecorr = 0;
resultname = maketemp(1);
if (!(fcopy = fopen_update_truncate(resultname)))
efaterror(resultname);
copystring();
#else
register int c;
declarecache;
register FILE *frew;
register 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 = cacheptr();
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(is_string)
#else
editstring(delta, is_string)
struct hshentry const *delta;
#endif
int is_string;
/*
* Read an edit script from finptr and applies it to the edit file.
#if !large_memory
* The result is written to fcopy.
* If delta, keyword expansion is performed simultaneously.
* If running out of lines in fedit, fedit and fcopy are swapped.
* editname 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.
* resultname 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;
long line_lim = LONG_MAX;
register RILE *fe;
# endif
register long i;
register RILE *fin;
# if large_memory
register 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,is_string,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 {
/* Copy lines without deleting any. */
copylines(dc.line1, delta);
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,true)){
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++, cacheptr());
# endif
for (;;) {
GETC_(frew, c)
if (c==SDELIM) {
GETC_(frew, c)
if (c!=SDELIM) {
if (--i)
editEndsPrematurely();
nextc = c;
uncache(fin);
return;
}
}
# if !large_memory
aputc_(c, f)
# endif
if (c == '\n')
break;
}
++rcsline;
} while (--i);
uncache(fin);
}
}
}
/* The rest is for keyword expansion */
int
expandline(infile, outfile, delta, delimstuffed, frewfile, dolog)
RILE *infile;
FILE *outfile, *frewfile;
struct hshentry const *delta;
int delimstuffed, dolog;
/*
* Read a line from INFILE and write it to OUTFILE.
* Do keyword expansion with data from DELTA.
* If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
* If FREWFILE is set, copy the line unchanged to FREWFILE.
* DELIMSTUFFED must be true if FREWFILE is set.
* Append revision history to log only if DOLOG 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])
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 */
uncache(infile);
keyreplace(matchresult, delta, ds, infile, out, dolog);
cache(infile);
e = 1;
break;
}
break;
}
}
keystring_eof:
*tp = 0;
aputs(keyval.string, out);
uncache_exit:
uncache(infile);
return r + e;
}
static void
escape_string(out, s)
register FILE *out;
register char const *s;
/* Output to OUT the string S, escaping chars that would break `ci -k'. */
{
register char c;
for (;;)
switch ((c = *s++)) {
case 0: return;
case '\t': aputs("\\t", out); break;
case '\n': aputs("\\n", out); break;
case ' ': aputs("\\040", out); break;
case KDELIM: aputs("\\044", out); break;
case '\\': if (VERSION(5)<=RCSversion) {aputs("\\\\", out); break;}
/* fall into */
default: aputc_(c, out) break;
}
}
char const ciklog[ciklogsize] = "checked in with -k by ";
static void
keyreplace(marker, delta, delimstuffed, infile, out, dolog)
enum markers marker;
register struct hshentry const *delta;
int delimstuffed;
RILE *infile;
register FILE *out;
int dolog;
/* function: outputs the keyword value(s) corresponding to marker.
* Attributes are derived from delta.
*/
{
register char const *sp, *cp, *date;
register int c;
register size_t cs, cw, ls;
char const *sp1;
char datebuf[datesize + zonelenmax];
int RCSv;
int exp;
sp = Keyword[(int)marker];
exp = Expand;
date = delta->date;
RCSv = RCSversion;
if (exp != VAL_EXPAND)
aprintf(out, "%c%s", KDELIM, sp);
if (exp != KEY_EXPAND) {
if (exp != VAL_EXPAND)
aprintf(out, "%c%c", 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:
escape_string(out,
marker==Id || RCSv<VERSION(4)
? basefilename(RCSname)
: getfullRCSname()
);
aprintf(out, " %s %s %s %s",
delta->num,
date2str(date, datebuf),
delta->author,
RCSv==VERSION(3) && delta->lockedby ? "Locked"
: delta->state
);
if (delta->lockedby)
if (VERSION(5) <= RCSv) {
if (locker_expansion || exp==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
|| exp == KEYVALLOCK_EXPAND
|| RCSv <= VERSION(4)
)
aputs(delta->lockedby, out);
break;
case Log:
case RCSfile:
escape_string(out, basefilename(RCSname));
break;
case Name:
if (delta->name)
aputs(delta->name, out);
break;
case Revision:
aputs(delta->num, out);
break;
case Source:
escape_string(out, getfullRCSname());
break;
case State:
aputs(delta->state, out);
break;
default:
break;
}
if (exp != VAL_EXPAND)
afputc(' ', out);
}
if (exp != VAL_EXPAND)
afputc(KDELIM, out);
if (marker == Log && dolog) {
struct buf leader;
sp = delta->log.string;
ls = delta->log.size;
if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
return;
bufautobegin(&leader);
if (RCSversion < VERSION(5)) {
cp = Comment.string;
cs = Comment.size;
} else {
int kdelim_found = 0;
Ioffset_type chars_read = Itell(infile);
declarecache;
setupcache(infile); cache(infile);
c = 0; /* Pacify `gcc -Wall'. */
/*
* Back up to the start of the current input line,
* setting CS to the number of characters before `$Log'.
*/
cs = 0;
for (;;) {
if (!--chars_read)
goto done_backing_up;
cacheunget_(infile, c)
if (c == '\n')
break;
if (c == SDELIM && delimstuffed) {
if (!--chars_read)
break;
cacheunget_(infile, c)
if (c != SDELIM) {
cacheget_(c)
break;
}
}
cs += kdelim_found;
kdelim_found |= c==KDELIM;
}
cacheget_(c)
done_backing_up:;
/* Copy characters before `$Log' into LEADER. */
bufalloc(&leader, cs);
cp = leader.string;
for (cw = 0; cw < cs; cw++) {
leader.string[cw] = c;
if (c == SDELIM && delimstuffed)
cacheget_(c)
cacheget_(c)
}
/* Convert traditional C or Pascal leader to ` *'. */
for (cw = 0; cw < cs; cw++)
if (ctab[(unsigned char) cp[cw]] != SPACE)
break;
if (
cw+1 < cs
&& cp[cw+1] == '*'
&& (cp[cw] == '/' || cp[cw] == '(')
) {
size_t i = cw+1;
for (;;)
if (++i == cs) {
warn(
"`%c* $Log' is obsolescent; use ` * $Log'.",
cp[cw]
);
leader.string[cw] = ' ';
break;
} else if (ctab[(unsigned char) cp[i]] != SPACE)
break;
}
/* Skip `$Log ... $' string. */
do {
cacheget_(c)
} while (c != KDELIM);
uncache(infile);
}
afputc('\n', out);
awrite(cp, cs, out);
sp1 = date2str(date, datebuf);
if (VERSION(5) <= RCSv) {
aprintf(out, "Revision %s %s %s",
delta->num, sp1, delta->author
);
} else {
/* oddity: 2 spaces between date and time, not 1 as usual */
sp1 = strchr(sp1, ' ');
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. */
cw = cs;
if (VERSION(5) <= RCSv)
for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw)
continue;
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');
}
}
bufautoend(&leader);
}
}
#if has_readlink
static int resolve_symlink P((struct buf*));
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;
int linkcount = MAXSYMLINKS;
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--) {
# ifndef ELOOP
/*
* Some pedantic Posix 1003.1-1990 hosts have readlink
* but not ELOOP. Approximate ELOOP with EMLINK.
*/
# define ELOOP EMLINK
# endif
errno = ELOOP;
return -1;
} else {
/* Splice symbolic link into L. */
b[r] = '\0';
L->string[
ROOTPATH(b) ? 0 : basefilename(L->string) - L->string
] = '\0';
bufscat(L, b);
}
e = errno;
bufautoend(&bigbuf);
errno = e;
switch (e) {
case readlink_isreg_errno: 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 RCSBUF.
* If (mustread & 0x2) then stat the RCS file and return
* the current finptr. Otherwise,
* try to open RCSBUF for reading and yield its RILE* descriptor.
* Put its status into *STATUS too.
* MUSTREAD is true (0x1) if the file must already exist, too.
* If all goes well, discard any previously acquired locks,
* and set fdlock to the file descriptor of the RCS lockfile.
*/
{
register char *tp;
register char const *sp, *RCSpath, *x;
RILE *f;
size_t l;
int e, exists, fdesc, fdescSafer, r, waslocked;
struct buf *dirt;
struct stat statbuf;
waslocked = 0 <= fdlock;
exists =
# if has_readlink
resolve_symlink(RCSbuf);
# else
stat(RCSbuf->string, &statbuf) == 0 ? 1
: errno==ENOENT ? 0 : -1;
# endif
if (exists < ((mustread & ~0x2) ||waslocked))
/*
* 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;
RCSpath = RCSbuf->string;
sp = basefilename(RCSpath);
l = sp - RCSpath;
dirt = &dirtpname[waslocked];
bufscpy(dirt, RCSpath);
tp = dirt->string + l;
x = rcssuffix(RCSpath);
# if has_readlink
if (!x) {
error("symbolic link to non RCS file `%s'", RCSpath);
errno = EINVAL;
return 0;
}
# endif
if (*sp == *x) {
error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
errno = EINVAL;
return 0;
}
/* Create a lock filename that 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++))
continue;
tp -= 2;
if (*tp == '_') {
error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
errno = EINVAL;
return 0;
}
*tp = '_';
}
sp = dirt->string;
f = 0;
/*
* good news:
* open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_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,OPEN_CREAT_READONLY) normally guarantees atomicity
* even with NFS.
* bad news:
* If you're root, (O_TRUNC,OPEN_CREAT_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.
* This can happen in the usual case of NFS over UDP.
* Suppose client A releases a lock by renaming ",f," to "f,v" at
* about the same time that client B obtains a lock by creating ",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 not only RCS, which uses open() and rename()
* to get and release locks; it also afflicts the traditional
* Unix method of using link() and unlink() to get and release locks,
* and the less traditional method of using mkdir() and rmdir().
* There is no easy workaround.
* 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.
*/
# if !open_can_creat
# define create(f) creat(f, OPEN_CREAT_READONLY)
# else
# define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_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);
fdescSafer = fdSafer(fdesc); /* Do it now; setrid might use stderr. */
e = errno;
setrid();
if (0 <= fdesc)
dirtpmaker[0] = effective;
if (fdescSafer < 0) {
if (e == EACCES && stat(sp,&statbuf) == 0)
/* The RCS file is busy. */
e = EEXIST;
} else {
e = ENOENT;
if (exists) {
if (!(mustread & 0x2)) { /* Normal case - open file */
f = Iopen(RCSpath, FOPEN_RB, status);
e = errno;
}
else { /* Just stat RCSpath */
f = finptr;
if (f) {stat(RCSpath, status); e = errno;}
else e = EINVAL;
}
if (f && waslocked) {
/* Discard the previous lock in favor of this one. */
ORCSclose();
seteid();
r = un_link(lockname);
e = errno;
setrid();
if (r != 0)
enfaterror(e, lockname);
bufscpy(&dirtpname[lockdirtp_index], sp);
}
}
fdlock = fdescSafer;
}
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 (dirtpname[i].string == name) {
dirtpmaker[i] = notmade;
return;
}
faterror("keepdirtemp");
}
char const *
makedirtemp(isworkfile)
int isworkfile;
/*
* Create a unique pathname and store it into dirtpname.
* Because of storage in tpnames, dirtempunlink() can unlink the file later.
* Return a pointer to the pathname created.
* If ISWORKFILE is 1, put it into the working file's directory;
* if 0, put the unique file in RCSfile's directory.
*/
{
register char *tp, *np;
register size_t dl;
register struct buf *bn;
register char const *name = isworkfile ? workname : RCSname;
dl = basefilename(name) - name;
bn = &dirtpname[newRCSdirtp_index + isworkfile];
bufalloc(bn,
# if has_mktemp
dl + 9
# else
strlen(name) + 3
# endif
);
bufscpy(bn, name);
np = tp = bn->string;
tp += dl;
*tp++ = '_';
*tp++ = '0'+isworkfile;
catchints();
# if has_mktemp
VOID strcpy(tp, "XXXXXX");
if (!mktemp(np) || !*np)
faterror("can't make temporary pathname `%.*s_%cXXXXXX'",
(int)dl, name, '0'+isworkfile
);
# else
/*
* Posix 1003.1-1990 has no reliable way
* to create a unique file in a named directory.
* We fudge here. If the filename 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
dirtpmaker[newRCSdirtp_index + isworkfile] = 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 = dirtpmaker[i]) != notmade) {
if (m == effective)
seteid();
VOID un_link(dirtpname[i].string);
if (m == effective)
setrid();
dirtpmaker[i] = notmade;
}
}
int
#if has_prototypes
chnamemod(
FILE **fromp, char const *from, char const *to,
int set_mode, mode_t mode, time_t mtime
)
/* The `#if has_prototypes' is needed because mode_t might promote to int. */
#else
chnamemod(fromp, from, to, set_mode, mode, mtime)
FILE **fromp; char const *from,*to;
int set_mode; mode_t mode; time_t mtime;
#endif
/*
* Rename a file (with stream pointer *FROMP) from FROM to TO, if TO not NULL.
* FROM already exists.
* If FROMP is NULL, then FROM is used to get handle to file.
* If 0 < SET_MODE, change the mode to MODE, before renaming if possible.
* If MTIME is not -1, change its mtime to MTIME before renaming.
* Close and clear *FROMP before renaming it (if not NULL).
* Unlink TO if it already exists.
* Return -1 on error (setting errno), 0 otherwise.
*/
{
mode_t mode_while_renaming = mode;
int fchmod_set_mode = 0;
if (to) {
# if bad_a_rename || bad_NFS_rename
struct stat st;
if (bad_NFS_rename || (bad_a_rename && set_mode <= 0)) {
if (!fromp) {
if (stat(from, &st) != 0)
return -1;
}
else if (fstat(fileno(*fromp), &st) != 0)
return -1;
if (bad_a_rename && set_mode <= 0)
mode = st.st_mode;
}
# endif
# if bad_a_rename
/*
* There's a short window of inconsistency
* during which the lock file is writable.
*/
mode_while_renaming = mode|S_IWUSR;
if (mode != mode_while_renaming)
set_mode = 1;
# endif
}
# if has_fchmod
if (0<set_mode && fromp &&
fchmod(fileno(*fromp),mode_while_renaming) == 0)
fchmod_set_mode = set_mode;
# endif
/* If bad_chmod_close, we must close before chmod. */
if (fromp) Ozclose(fromp);
if (fchmod_set_mode<set_mode && chmod(from, mode_while_renaming) != 0)
return -1;
if (setmtime(from, mtime) != 0)
return -1;
if (!to) return 0;
# if !has_rename || bad_b_rename
/*
* There's a short window of inconsistency
* during which TO does not exist.
*/
if (un_link(to) != 0 && errno != ENOENT)
return -1;
# endif
# if has_rename
if (rename(from,to) != 0 && !(has_NFS && errno==ENOENT))
return -1;
# else
if (do_link(from,to) != 0 || un_link(from) != 0)
return -1;
# endif
# if bad_NFS_rename
{
/*
* Check whether the rename falsely reported success.
* A race condition can occur between the rename and the stat.
*/
struct stat tostat;
if (stat(to, &tostat) != 0)
return -1;
if (! same_file(st, tostat, 0)) {
errno = EIO;
return -1;
}
}
# endif
# if bad_a_rename
if (0 < set_mode && chmod(to, mode) != 0)
return -1;
# endif
return 0;
}
int
setmtime(file, mtime)
char const *file;
time_t mtime;
/* Set FILE's last modified time to MTIME, but do nothing if MTIME is -1. */
{
static struct utimbuf amtime; /* static so unused fields are zero */
if (mtime == -1)
return 0;
amtime.actime = now();
amtime.modtime = mtime;
return utime(file, &amtime);
}
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 rcslock *next, **trail, **found;
found = 0;
for (trail = &Locks; (next = *trail); trail = &next->nextlock)
if (strcmp(getcaller(), next->login) == 0) {
if (found) {
rcserror("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 = 0;
*found = next->nextlock;
}
return 1;
}
int
checklock(delta, target )
char const *delta; /* numeric string */
struct hshentry **target;
/* function: Check whether a lock exists on delta, and break it.
* Break succeeds unless the caller is not the owner of
* the lock and notification of the owner (if requested)
* fails.
*
* Notification is not sent (sendmail automatically succeeds)
* if suppress_mail is set.
*
* Notification automatically fails if not suppressed, but no
* message was provided (!rcsMailtxt).
*
* Returns:
* 0 - no lock set
* 1 - lock set (and broken successfully, if requested)
* -1 - lock not broken (break requested, but unable to break)
*
* Pointer to (formerly) locked delta, if any, is also returned,
* if requested (target not NULL).
*
* Sends mail if a lock different from the caller's is broken.
* Prints an error message if break is requested and existing lock not broken.
*/
{
register struct rcslock *next, **trail;
rbool trysend = true;
for (trail = &Locks; (next = *trail); trail = &next->nextlock)
if (strcmp(delta, next->delta->num) == 0) {
if (target) *target = next->delta;
/* if (!dobreak) return 1; */
/* Cannot send mail if there is no body to send */
if (!suppress_mail && !rcsMailtxt) trysend = false;
/* If lock owned by someone else, send mail
* (unless asked not to)
*/
if (
strcmp(getcaller(),next->login) != 0
&& (!trysend || !sendmail(delta, next->login,true))
) {
rcserror("revision %s still locked by %s",
delta, next->login
);
return -1;
}
diagnose("%s unlocked\n", next->delta->num);
*trail = next->nextlock;
next->delta->lockedby = 0;
return 1;
}
return 0;
}
int
addlock(delta, verbose)
struct hshentry * delta;
int verbose;
/*
* Add a lock held by caller to DELTA and yield 1 if successful.
* Print an error message if verbose 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 rcslock *next;
for (next = Locks; next; next = next->nextlock)
if (cmpnum(delta->num, next->delta->num) == 0)
if (strcmp(getcaller(), next->login) == 0)
return 0;
else {
if (verbose)
rcserror("Revision %s is already locked by %s.",
delta->num, next->login
);
else nerror++; /* rcserror also does this */
return -1;
}
next = ftalloc(struct rcslock);
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 -1 if unsuccessful, 0 if no change, 1 if change.
*/
{
register struct assoc *next;
for (next = Symbols; next; next = next->nextassoc)
if (strcmp(name, next->symbol) == 0)
if (strcmp(next->num,num) == 0)
return 0;
else if (rebind) {
next->num = num;
return 1;
} else {
rcserror("symbolic name %s already bound to %s",
name, next->num
);
return -1;
}
next = ftalloc(struct assoc);
next->symbol = name;
next->num = num;
next->nextassoc = Symbols;
Symbols = next;
return 1;
}
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));
rcserror("user %s not on the access list", getcaller());
return false;
}
#ifdef RCSLIBRARY /* LATER */
/* Streamlined lookupnameval(), which only retrieves application namespace
* values. If the request if for something else (e.g. a standard RCS field),
* NULL is returned; no error is indicated.
*/
struct rcsNameVal *
getnameval(nvptr)
register struct rcsNameVal *nvptr;
{
struct rcsNameVal *ret;
struct buf numericrev; /* expanded revision number */
struct hshentry *targetdelta; /* revision containing name */
struct hshentries *gendeltas; /* deltas needed to generate rev */
const char *val; /* returned value */
int rc;
rbool b;
/* Must be application namespace (not standard RCS field) */
if (!nvptr->namespace || !*nvptr->namespace) return NULL;
if (!nvptr->revision || !*nvptr->revision) { /* file hdr namespace */
return get1nameval(rcsNameSpace, nvptr);
}
/* Otherwise, we are looking for a particular revision's namepace */
/* ensure that we have read in the delta tree */
gettree();
/* Find the right delta */
bufautobegin(&numericrev);
b = !expandsym(nvptr->revision, &numericrev) ||
!(targetdelta = genrevs(numericrev.string, 0,0,0,&gendeltas));
bufautoend(&numericrev);
if (b) return NULL;
return get1nameval(targetdelta->namesp, nvptr);
}
static struct rcsNameVal *
get1nameval(nsptr, nv_in)
struct namespace *nsptr;/* list of candidate namespace blocks */
register struct rcsNameVal *nv_in; /* caller's lookup request */
{
/* find aplname.keyword value from among list of namespaces */
struct rcsNameVal *ret;
register struct nameval *nvptr;
/* find matching namespace block from list */
for (; nsptr && strcmp(nsptr->namesp, nv_in->namespace);
nsptr = nsptr->next);
if (!nsptr) return NULL;
/* find matching keyword from list */
for (nvptr = nsptr->nvhead; nvptr && strcmp(nvptr->name, nv_in->name);
nvptr = nvptr->next);
if (!nvptr) return NULL;
/* Construct return struct */
ret = ftalloc(struct rcsNameVal);
*ret = *nv_in; /* copies pointers to namespace, name, revision */
ret->val = nvptr->val;
return ret;
}
/* Return all name/val pairs in a given namespace (or all namespaces, if none
* specified), belonging to a particular revision (or file header). The
* revision/header containing the namespace(s) is fixed for the duration
* of a search (and is implied by the list of namespaces to look through,
* as a namespace list is "owned" by a particular revision).
*/
static const char *getrev; /* revision (or "") owning namespaces */
static struct namespace *nslist; /* list of namespaces to look through */
static rbool matchall; /* do we match all namespaces, or just
* a single one?
*/
static struct nameval *nvreturn; /* next nameval in namespace */
struct rcsNameVal *
getfirstnameval(rev, space, nsptr)
const char *rev; /* revision (or file hdr) owning namespaces */
const char *space; /* namespace name to match */
struct namespace *nsptr;/* list of namespaces for the hdr, or revision*/
{
/* Set up for getnextnameval(), by initializing static variables,
* and locating first matching namespace, and within it, the
* first name/val pair
*/
nslist = nsptr;
getrev = rev;
/* Find matching namespace, if required */
if (!(matchall = (space == NULL)))
for (; nslist && strcmp(nslist->namesp, space);
nslist = nslist->next);
if (!nslist) return NULL;
nvreturn = nslist->nvhead;
return getnextnameval();
}
struct rcsNameVal *
getnextnameval()
{
/* Get the next namespace name/val pair: either return the one
* already found (advancing state to the next pair), or look
* for the next namespace containing a name/val pair, and return that.
*/
struct rcsNameVal *ret;
for(;;) {
/* nvreturn has been set to point to the next name/val pair */
if (nvreturn) {
ret = ftalloc(struct rcsNameVal);
/* Fill in application (namespace), name, value, revision */
ret->namespace = nslist->namesp;
ret->name = nvreturn->name;
ret->val = nvreturn->val;
ret->revision = getrev;
nvreturn = nvreturn->next;
return ret;
}
/* If no more in the current namespace, advance to next namespace */
if (!matchall) { /* only look through one namespace */
return NULL;
}
if ((nslist = nslist->next)) {
nvreturn = nslist->nvhead;
continue;
}
/* No more namespaces to consider */
return NULL;
}
}
int
addnameval(revno, aplname, keyword, val, override)
const char *revno;
const char *aplname;
const char *keyword;
const char *val;
rbool override;
/*
* Add a name/value pair to the name space described by list.
* Returns ERR_BAD_OLDREV if the specified revision does not exist.
* Returns ERRR_APPL_ARGS if the keyword is already in the name space
* (unless override is set).
*/
{
struct namespace **nsptrptr; /* pointer to pointer to cur namespace*/
struct namespace *nsptr; /* pointer to matching namespace */
struct nameval *nvptr; /* pointer to current name/val struct */
struct buf numericrev; /* expanded revision number */
struct hshentry *targetdelta; /* revision containing name */
struct hshentries *gendeltas; /* deltas needed to generate rev */
/* find application block head, from revno */
if (!revno || !*revno) nsptrptr = &rcsNameSpace;
else {
bufautobegin(&numericrev);
if (expandsym(revno, &numericrev))
targetdelta = genrevs(numericrev.string, 0,0,0,&gendeltas);
else targetdelta = NULL;
bufautoend(&numericrev);
if (!targetdelta) return ERR_BAD_OLDREV;
nsptrptr = &targetdelta->namesp;
}
/* Search for matching name space */
while (*nsptrptr && strcmp((*nsptrptr)->namesp, aplname))
nsptrptr = &((*nsptrptr)->next);
if (!*nsptrptr) { /* need to create new name space */
nsptr = ftalloc(struct namespace);
nsptr->namesp = fstr_save(aplname);
nsptr->nvhead = NULL;
nsptr->next = NULL;
*nsptrptr = nsptr;
}
else nsptr = *nsptrptr;
/* Check for existing symbol in namespace */
for (nvptr = nsptr->nvhead; nvptr && strcmp(nvptr->name, keyword);
nvptr = nvptr->next);
/* Does symbol already exist? */
if (nvptr) {
if (override) {
nvptr->val = val ? fstr_save(val) : "";
return 0;
}
return ERR_APPL_ARGS;
}
/* Allocate new name/val pair */
nvptr = ftalloc(struct nameval);
nvptr->name = fstr_save(keyword);
nvptr->val = val ? fstr_save(val) : "";
/* Link to head of list of name/val pairs */
nvptr->next = nsptr->nvhead;
nsptr->nvhead = nvptr;
return 0;
}
#endif /* RCSLIBRARY */
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 0 on success, -1 on failure.
*/
{
int r = 0, e;
if (lockflag)
if (changed) {
if (changed < 0)
return -1;
putadmin();
puttree(Head, frewrite);
aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
foutptr = frewrite;
} else {
int nr = !!frewrite, ne = 0;
ORCSclose();
seteid();
ignoreints();
# if !bad_creat0
if (rcsAtomic)
# endif
if (nr) {
nr = un_link(newRCSname);
ne = errno;
keepdirtemp(newRCSname);
}
r = un_link(lockname);
e = errno;
keepdirtemp(lockname);
restoreints();
setrid();
if (r != 0)
enerror(e, lockname);
# if !bad_creat0
if (rcsAtomic)
# endif
if (nr != 0) {
enerror(ne, newRCSname);
r = -1;
}
}
return r;
}
int
donerewrite(changed, newRCStime)
int changed;
time_t newRCStime;
/*
* Finish rewriting an RCS file if CHANGED is nonzero.
* Set its mode if CHANGED is positive.
* Set its modification time to NEWRCSTIME unless it is -1.
* Return 0 on success, -1 on failure.
*/
{
int r = 0, e = 0;
# if bad_creat0
int lr, le;
# endif
if (changed && !nerror) {
if (finptr) {
fastcopy(finptr, frewrite);
}
if (1 < RCSstat.st_nlink)
rcswarn("breaking hard link");
aflush(frewrite);
if (rcsAtomic) { /* just remember, don't commit now */
Ozclose(&frewrite);
r = updateObjAdd(
&updateObj, newRCSname, RCSname,
lockname, changed,
RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
newRCStime);
if (r < 0) { /* out of memory */
updateObjAbort(&updateObj);
updateObjClear(&updateObj);
}
return r;
}
seteid();
ignoreints();
r = chnamemod(
&frewrite, newRCSname, RCSname, changed,
RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
newRCStime
);
e = errno;
keepdirtemp(newRCSname);
# if bad_creat0
lr = un_link(lockname);
le = errno;
keepdirtemp(lockname);
# endif
restoreints();
setrid();
if (r != 0) {
enerror(e, RCSname);
error("saved in %s", newRCSname);
}
# if bad_creat0
if (lr != 0) {
enerror(le, lockname);
r = -1;
}
# endif
}
return r;
}
int
sendmail(Delta, who, haveMsg)
char const *Delta, *who;
int haveMsg;
/* Function: mail to who, informing him that his lock on delta was
* broken by caller. Ask first whether to go ahead. Return false on
* error or if user decides not to break the lock. If "haveMsg" is
* set, the caller has already provided (in rcsMailtxt) the body
* of the message to send, thus user is not informed of the need
* to break the lock.
*/
{
#ifdef SENDMAIL
char const *messagefile;
int old1, old2, c, status;
FILE * mailmess;
#endif
aprintf(finform, "Revision %s is already locked by %s.\n", Delta, who);
if (suppress_mail)
return true;
if (!haveMsg &&
!yesorno(false, "Do you want to break the lock? [ny](n): "))
return false;
/* go ahead with breaking */
#ifdef SENDMAIL
messagefile = maketemp(0);
if (!(mailmess = fopenSafer(messagefile, "w+"))) {
efaterror(messagefile);
}
aprintf(mailmess, "Subject: Broken lock on %s\n\nYour lock on revision %s of file %s\nhas been broken by %s for the following reason:\n",
basefilename(RCSname), Delta, getfullRCSname(), getcaller()
);
if (!haveMsg) {
aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr);
eflush();
old1 = '\n'; old2 = ' ';
for (; ;) {
c = getcstdin();
if (feof(stdin)) {
aprintf(mailmess, "%c\n", old1);
break;
}
else if ( c == '\n' && old1 == '.' && old2 == '\n')
break;
else {
afputc(old1, mailmess);
old2 = old1; old1 = c;
if (c == '\n') {
aputs(">> ", stderr);
eflush();
}
}
}
}
else awrite(rcsMailtxt, strlen(rcsMailtxt), mailmess);
Orewind(mailmess);
aflush(mailmess);
status = run(fileno(mailmess), (char*)0, SENDMAIL, who, (char*)0);
Ozclose(&mailmess);
if (status == 0)
return true;
warn("Mail failed.");
#endif
warn("Mail notification of broken locks is not available.");
warn("Please tell `%s' why you broke the lock.", who);
return(true);
}
void
ORCSclose()
{
if (0 <= fdlock) {
if (close(fdlock) != 0)
efaterror(lockname);
fdlock = -1;
}
Ozclose(&frewrite);
}
void
ORCSerror()
/*
* Like ORCSclose, except we are cleaning up after an interrupt or fatal error.
* Do not report errors, since this may loop. This is needed only because
* some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and
* some nearly-Posix hosts (e.g. NFS) work better if the files are closed first.
* This isn't a completely reliable away to work around brain-damaged hosts,
* because of the gap between actual file opening and setting frewrite etc.,
* but it's better than nothing.
*/
{
if (0 <= fdlock)
VOID close(fdlock);
if (frewrite)
/* Avoid fclose, since stdio may not be reentrant. */
VOID close(fileno(frewrite));
}
/* The update Object services */
static const struct updateEntry *
getObjFirst(Obj)
struct updateObj *Obj;
{
if (!Obj->tailptr) return NULL; /* Empty list - no last element */
Obj->curptr = &Obj->Head;
return Obj->curptr;
}
static struct updateEntry *
getObjNext (Obj)
struct updateObj *Obj;
{
if (!Obj->curptr) return NULL; /* protection */
if (Obj->curptr == Obj->tailptr) { /* end of list */
Obj->curptr = NULL; /* don't go past end */
}
else Obj->curptr = Obj->curptr->next; /* next item in list, if any */
return Obj->curptr;
}
static void
updateObjClear(Obj)
struct updateObj *Obj;
{
Obj->tailptr = NULL; /* no entries in list */
Obj->curptr = NULL; /* haven't done a getfirst yet */
}
static int
updateObjAdd (Obj, newname, oldname, lockfname, set_mode, mode, newtime)
struct updateObj *Obj;
const char *newname;
const char *oldname;
const char *lockfname;
int set_mode;
mode_t mode;
time_t newtime;
{
/* Add a file to the list of files to be updated */
register struct updateEntry *ueptr; /* pointer to new entry */
/* Locate (or allocate) next list entry to fill */
if (!Obj->tailptr) { /* no entries in list, yet */
ueptr = &Obj->Head;
}
else {
if (!(ueptr = Obj->tailptr->next)) {
if (!(ueptr = calloc(1, sizeof(struct updateEntry)))) {
return ERR_NO_MEMORY;
}
Obj->tailptr->next = ueptr; /* add new struct to end */
}
}
Obj->tailptr = ueptr; /* advance pointer to last real entry */
bufscpy(&ueptr->archive, oldname);
bufscpy(&ueptr->tmpfile, newname);
bufscpy(&ueptr->lockfile, lockfname);
ueptr->set_mode = set_mode;
ueptr->mode = mode;
ueptr->time = newtime;
keepdirtemp(newname); /* don't remove files until changes */
keepdirtemp(lockfname); /* are applied */
return 0;
}
static int
updateObjApply (Obj)
struct updateObj *Obj;
{
register const struct updateEntry *ueptr;
int rc = 0;
int r;
if (!(ueptr = getObjFirst(Obj))) return 0;
seteid();
ignoreints();
/* Rename all temp (new) files to archive (old) files */
do {
r = chnamemod(NULL, ueptr->tmpfile.string,
ueptr->archive.string,
ueptr->set_mode,
ueptr->mode,
ueptr->time);
if (r != 0) {
enerror(errno, ueptr->archive.string);
error("saved in %s", ueptr->tmpfile.string);
}
if (rc >= 0) rc = r;
} while ((ueptr = getObjNext(Obj)));
/* Remove all lock files *after* all renames are finished */
ueptr = getObjFirst(Obj); /* we know it's not null */
do {
r = un_link(ueptr->lockfile.string);
if (r != 0) {
enerror(errno, ueptr->lockfile.string);
r = -1;
}
if (rc >= 0) rc = r;
} while ((ueptr = getObjNext(Obj)));
restoreints();
setrid();
return rc;
}
static int
updateObjAbort(Obj)
struct updateObj *Obj;
{
register const struct updateEntry *ueptr;
int rc = 0;
int r;
if (!(ueptr = getObjFirst(Obj))) return 0;
seteid();
ignoreints();
/* We can unlink both lockfile and tmpfile in single loop,
* since nothing is changed
*/
do {
r = un_link(ueptr->tmpfile.string);
if (r != 0) {
enerror(errno, ueptr->tmpfile.string);
r = -1;
}
if (rc >= 0) rc = r;
r = un_link(ueptr->lockfile.string);
if (r != 0) {
enerror(errno, ueptr->lockfile.string);
r = -1;
}
if (rc >= 0) rc = r;
} while ((ueptr = getObjNext(Obj)));
restoreints();
setrid();
return rc;
}
int
rcscommit(doabort)
int doabort; /* should we abort transaction? */
{
int rc = doabort ?
updateObjAbort(&updateObj) :
updateObjApply(&updateObj);
updateObjClear(&updateObj);
/* Could add code to set return code based on errno, if
* rc == -1 (because all failures are due to failed system calls)
*/
return rc;
}