freebsd-dev/gnu/usr.bin/rcs/lib/rcsedit.c

1954 lines
47 KiB
C
Raw Normal View History

/* RCS stream editor */
/******************************************************************************
1993-06-18 04:22:21 +00:00
* edits the input file according to a
* script from stdin, generated by diff -n
* performs keyword expansion
******************************************************************************
1993-06-18 04:22:21 +00:00
*/
/* Copyright 1982, 1988, 1989 Walter Tichy
Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
1993-06-18 04:22:21 +00:00
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.
1993-06-18 04:22:21 +00:00
Report problems and direct all questions to:
rcs-bugs@cs.purdue.edu
*/
/*
* 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.
1995-05-30 05:05:38 +00:00
*
* (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;
*
1993-06-18 04:22:21 +00:00
* 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
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* Revision 4.7 88/11/08 13:54:14 narten
* misplaced semicolon caused infinite loop
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* Revision 4.6 88/08/09 19:12:45 eggert
* Shrink stdio code size; allow cc -R.
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* 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)
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* Revision 4.4 87/10/18 10:32:21 narten
* Updating version numbers. Changes relative to version 1.1 actually
* relative to 4.1
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* Revision 1.4 87/09/24 13:59:29 narten
1995-05-30 05:05:38 +00:00
* Sources now pass through lint (if you ignore printf/sprintf/fprintf
1993-06-18 04:22:21 +00:00
* warnings)
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* 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
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* Revision 1.2 87/03/27 14:22:17 jenkins
* Port to suns
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* 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.
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* 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.
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* 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@@.
1995-05-30 05:05:38 +00:00
*
1993-06-18 04:22:21 +00:00
* 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"
1999-08-27 23:37:10 +00:00
libId(editId, "$FreeBSD$")
1993-06-18 04:22:21 +00:00
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));
1993-06-18 04:22:21 +00:00
FILE *fcopy; /* result file descriptor */
char const *resultname; /* result pathname */
1993-06-18 04:22:21 +00:00
int locker_expansion; /* should the locker name be appended to Id val? */
#if !large_memory
static RILE *fedit; /* edit file descriptor */
static char const *editname; /* edit pathname */
1993-06-18 04:22:21 +00:00
#endif
static long editline; /* edit line counter; #lines before cursor */
1993-06-18 04:22:21 +00:00
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)
1993-06-18 04:22:21 +00:00
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)
1993-06-18 04:22:21 +00:00
#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;
}
1993-06-18 04:22:21 +00:00
}
# 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*));
1993-06-18 04:22:21 +00:00
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;
1993-06-18 04:22:21 +00:00
}
# endif
#endif
static void
1993-06-18 04:22:21 +00:00
editEndsPrematurely()
{
fatserror("edit script ends prematurely");
}
static void
1993-06-18 04:22:21 +00:00
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));
1993-06-18 04:22:21 +00:00
static void
movelines(s1, s2, n)
register Iptr_type *s1;
register Iptr_type const *s2;
register long n;
1993-06-18 04:22:21 +00:00
{
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));
1993-06-18 04:22:21 +00:00
/*
* `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.
1993-06-18 04:22:21 +00:00
*
* 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;
1993-06-18 04:22:21 +00:00
static void
insertline(n, l)
long n;
1993-06-18 04:22:21 +00:00
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;
1993-06-18 04:22:21 +00:00
/* Delete lines N through N+NLINES-1. N is 0-origin. */
{
long l = n + nlines;
1993-06-18 04:22:21 +00:00
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)
1993-06-18 04:22:21 +00:00
} 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)
1993-06-18 04:22:21 +00:00
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;
1993-06-18 04:22:21 +00:00
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;
1993-06-18 04:22:21 +00:00
}
}
}
/* Open a temporary NAME for output, truncating any previous contents. */
# define fopen_update_truncate(name) fopenSafer(name, FOPEN_W_WORK)
1993-06-18 04:22:21 +00:00
#else /* !large_memory */
static FILE * fopen_update_truncate P((char const*));
1993-06-18 04:22:21 +00:00
static FILE *
fopen_update_truncate(name)
char const *name;
1993-06-18 04:22:21 +00:00
{
if (bad_fopen_wplus && un_link(name) != 0)
efaterror(name);
return fopenSafer(name, FOPEN_WPLUS_WORK);
1993-06-18 04:22:21 +00:00
}
#endif
void
openfcopy(f)
FILE *f;
{
if (!(fcopy = f)) {
if (!resultname)
resultname = maketemp(2);
if (!(fcopy = fopen_update_truncate(resultname)))
efaterror(resultname);
1993-06-18 04:22:21 +00:00
}
}
#if !large_memory
static void swapeditfiles P((FILE*));
1993-06-18 04:22:21 +00:00
static void
swapeditfiles(outfile)
FILE *outfile;
/* Function: swaps resultname and editname, assigns fedit=fcopy,
1993-06-18 04:22:21 +00:00
* and rewinds fedit for reading. Set fcopy to outfile if nonnull;
* otherwise, set fcopy to be resultname opened for reading and writing.
1993-06-18 04:22:21 +00:00
*/
{
char const *tmpptr;
editline = 0; linecorr = 0;
Orewind(fcopy);
1993-06-18 04:22:21 +00:00
fedit = fcopy;
tmpptr=editname; editname=resultname; resultname=tmpptr;
1993-06-18 04:22:21 +00:00
openfcopy(outfile);
}
void
snapshotedit(f)
FILE *f;
/* Copy the current state of the edits to F. */
{
finishedit((struct hshentry *)0, (FILE*)0, false);
1993-06-18 04:22:21 +00:00
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.
1993-06-18 04:22:21 +00:00
* 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))
1993-06-18 04:22:21 +00:00
;
} 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*));
1993-06-18 04:22:21 +00:00
static void
copylines(upto, delta)
register long upto;
1993-06-18 04:22:21 +00:00
struct hshentry const *delta;
/*
* Copy input lines editline+1..upto from fedit to fcopy.
* If delta, keyword expansion is done simultaneously.
1993-06-18 04:22:21 +00:00
* 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);
1993-06-18 04:22:21 +00:00
/* 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();
1993-06-18 04:22:21 +00:00
} while (++editline < upto);
else {
setupcache(fe); cache(fe);
do {
do {
cachegeteof_(c, editLineNumberOverflow();)
aputc_(c, fc)
1993-06-18 04:22:21 +00:00
} 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;
1993-06-18 04:22:21 +00:00
}
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)
1993-06-18 04:22:21 +00:00
switch (c) {
case '\n':
++editline;
++rcsline;
amidline = false;
break;
case SDELIM:
GETC_(frew,c)
1993-06-18 04:22:21 +00:00
if (c != SDELIM) {
/* end of string */
nextc = c;
editline += amidline;
uncache(fin);
return;
}
/* fall into */
default:
amidline = true;
break;
}
aputc_(c,fcop)
1993-06-18 04:22:21 +00:00
}
}
void
enterstring()
/* Like copystring, except the string is put into the edit data structure. */
{
#if !large_memory
editname = 0;
1993-06-18 04:22:21 +00:00
fedit = 0;
editline = linecorr = 0;
resultname = maketemp(1);
if (!(fcopy = fopen_update_truncate(resultname)))
efaterror(resultname);
1993-06-18 04:22:21 +00:00
copystring();
#else
register int c;
declarecache;
register FILE *frew;
register long e, oe;
1993-06-18 04:22:21 +00:00
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)
1993-06-18 04:22:21 +00:00
oamidline = amidline;
oe = e;
switch (c) {
case '\n':
++e;
++rcsline;
amidline = false;
break;
case SDELIM:
GETC_(frew,c)
1993-06-18 04:22:21 +00:00
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, keyword expansion is performed simultaneously.
1993-06-18 04:22:21 +00:00
* If running out of lines in fedit, fedit and fcopy are swapped.
* editname is the name of the file that goes with fedit.
1993-06-18 04:22:21 +00:00
#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.
1993-06-18 04:22:21 +00:00
* 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;
1993-06-18 04:22:21 +00:00
register RILE *fe;
# endif
register long i;
1993-06-18 04:22:21 +00:00
register RILE *fin;
# if large_memory
register long j;
1993-06-18 04:22:21 +00:00
# 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; } )
1993-06-18 04:22:21 +00:00
} while (c != '\n');
} while (--i);
# endif
} else {
/* Copy lines without deleting any. */
copylines(dc.line1, delta);
1993-06-18 04:22:21 +00:00
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)){
1993-06-18 04:22:21 +00:00
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());
1993-06-18 04:22:21 +00:00
# endif
for (;;) {
GETC_(frew, c)
1993-06-18 04:22:21 +00:00
if (c==SDELIM) {
GETC_(frew, c)
1993-06-18 04:22:21 +00:00
if (c!=SDELIM) {
if (--i)
editEndsPrematurely();
nextc = c;
uncache(fin);
return;
}
}
# if !large_memory
aputc_(c, f)
# endif
if (c == '\n')
break;
1993-06-18 04:22:21 +00:00
}
++rcsline;
} while (--i);
uncache(fin);
}
}
}
/* The rest is for keyword expansion */
int
expandline(infile, outfile, delta, delimstuffed, frewfile, dolog)
1993-06-18 04:22:21 +00:00
RILE *infile;
FILE *outfile, *frewfile;
struct hshentry const *delta;
int delimstuffed, dolog;
1993-06-18 04:22:21 +00:00
/*
* Read a line from INFILE and write it to OUTFILE.
* Do keyword expansion with data from DELTA.
1993-06-18 04:22:21 +00:00
* 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.
1993-06-18 04:22:21 +00:00
* 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;)
1993-06-18 04:22:21 +00:00
for (;;) {
switch (c) {
case SDELIM:
if (ds) {
GETC_(frew, c)
1993-06-18 04:22:21 +00:00
if (c != SDELIM) {
/* end of string */
nextc=c;
goto uncache_exit;
}
}
/* fall into */
default:
aputc_(c,out)
1993-06-18 04:22:21 +00:00
r = 0;
break;
case '\n':
rcsline += ds;
aputc_(c,out)
1993-06-18 04:22:21 +00:00
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])
1993-06-18 04:22:21 +00:00
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;)
1993-06-18 04:22:21 +00:00
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)
1993-06-18 04:22:21 +00:00
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);
1993-06-18 04:22:21 +00:00
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;
}
}
1993-06-18 04:22:21 +00:00
char const ciklog[ciklogsize] = "checked in with -k by ";
static void
keyreplace(marker, delta, delimstuffed, infile, out, dolog)
1993-06-18 04:22:21 +00:00
enum markers marker;
register struct hshentry const *delta;
int delimstuffed;
RILE *infile;
1993-06-18 04:22:21 +00:00
register FILE *out;
int dolog;
1993-06-18 04:22:21 +00:00
/* function: outputs the keyword value(s) corresponding to marker.
* Attributes are derived from delta.
*/
{
register char const *sp, *cp, *date;
register int c;
1993-06-18 04:22:21 +00:00
register size_t cs, cw, ls;
char const *sp1;
char datebuf[datesize + zonelenmax];
1993-06-18 04:22:21 +00:00
int RCSv;
int exp;
1993-06-18 04:22:21 +00:00
sp = Keyword[(int)marker];
exp = Expand;
date = delta->date;
1993-06-18 04:22:21 +00:00
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,
1993-06-18 04:22:21 +00:00
marker==Log && RCSv<VERSION(5) ? '\t' : ' '
);
switch (marker) {
case Author:
1993-06-18 04:22:21 +00:00
aputs(delta->author, out);
break;
case Date:
1993-06-18 04:22:21 +00:00
aputs(date2str(date,datebuf), out);
break;
case Id:
case LocalId:
case Header:
case CVSHeader:
if (marker == Id || RCSv < VERSION(4) ||
(marker == LocalId && LocalIdMode == Id))
escape_string(out, basefilename(RCSname));
else if (marker == CVSHeader ||
(marker == LocalId && LocalIdMode == CVSHeader))
escape_string(out, getfullCVSname());
else
escape_string(out, getfullRCSname());
aprintf(out, " %s %s %s %s",
1993-06-18 04:22:21 +00:00
delta->num,
date2str(date, datebuf),
delta->author,
RCSv==VERSION(3) && delta->lockedby ? "Locked"
: delta->state
);
if (delta->lockedby)
1993-06-18 04:22:21 +00:00
if (VERSION(5) <= RCSv) {
if (locker_expansion || exp==KEYVALLOCK_EXPAND)
1993-06-18 04:22:21 +00:00
aprintf(out, " %s", delta->lockedby);
} else if (RCSv == VERSION(4))
aprintf(out, " Locker: %s", delta->lockedby);
break;
case Locker:
1993-06-18 04:22:21 +00:00
if (delta->lockedby)
if (
locker_expansion
|| exp == KEYVALLOCK_EXPAND
1993-06-18 04:22:21 +00:00
|| RCSv <= VERSION(4)
)
aputs(delta->lockedby, out);
break;
case Log:
case RCSfile:
escape_string(out, basefilename(RCSname));
1993-06-18 04:22:21 +00:00
break;
case Name:
if (delta->name)
aputs(delta->name, out);
break;
case Revision:
1993-06-18 04:22:21 +00:00
aputs(delta->num, out);
break;
case Source:
escape_string(out, getfullRCSname());
1993-06-18 04:22:21 +00:00
break;
case State:
1993-06-18 04:22:21 +00:00
aputs(delta->state, out);
break;
default:
1993-06-18 04:22:21 +00:00
break;
}
if (exp != VAL_EXPAND)
1993-06-18 04:22:21 +00:00
afputc(' ', out);
}
if (exp != VAL_EXPAND)
afputc(KDELIM, out);
if (marker == Log && dolog) {
struct buf leader;
1993-06-18 04:22:21 +00:00
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);
}
1993-06-18 04:22:21 +00:00
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
);
}
1993-06-18 04:22:21 +00:00
/* Do not include state: it may change and is not updated. */
cw = cs;
1993-06-18 04:22:21 +00:00
if (VERSION(5) <= RCSv)
for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw)
continue;
1993-06-18 04:22:21 +00:00
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);
1993-06-18 04:22:21 +00:00
}
}
#if has_readlink
static int resolve_symlink P((struct buf*));
1993-06-18 04:22:21 +00:00
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;
1993-06-18 04:22:21 +00:00
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
1993-06-18 04:22:21 +00:00
errno = ELOOP;
return -1;
} else {
/* Splice symbolic link into L. */
b[r] = '\0';
L->string[
ROOTPATH(b) ? 0 : basefilename(L->string) - L->string
] = '\0';
1993-06-18 04:22:21 +00:00
bufscat(L, b);
}
e = errno;
bufautoend(&bigbuf);
errno = e;
switch (e) {
case readlink_isreg_errno: return 1;
1993-06-18 04:22:21 +00:00
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.
* Then try to open RCSBUF for reading and yield its RILE* descriptor.
1993-06-18 04:22:21 +00:00
* 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 fdlock to the file descriptor of the RCS lockfile.
1993-06-18 04:22:21 +00:00
*/
{
register char *tp;
register char const *sp, *RCSpath, *x;
1993-06-18 04:22:21 +00:00
RILE *f;
size_t l;
int e, exists, fdesc, fdescSafer, r, waslocked;
1993-06-18 04:22:21 +00:00
struct buf *dirt;
struct stat statbuf;
waslocked = 0 <= fdlock;
1993-06-18 04:22:21 +00:00
exists =
# if has_readlink
resolve_symlink(RCSbuf);
# else
stat(RCSbuf->string, &statbuf) == 0 ? 1
: errno==ENOENT ? 0 : -1;
# endif
if (exists < (mustread|waslocked))
1993-06-18 04:22:21 +00:00
/*
* 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);
1993-06-18 04:22:21 +00:00
tp = dirt->string + l;
x = rcssuffix(RCSpath);
1993-06-18 04:22:21 +00:00
# if has_readlink
if (!x) {
error("symbolic link to non RCS file `%s'", RCSpath);
1993-06-18 04:22:21 +00:00
errno = EINVAL;
return 0;
}
# endif
if (*sp == *x) {
error("RCS pathname `%s' incompatible with suffix `%s'", sp, x);
1993-06-18 04:22:21 +00:00
errno = EINVAL;
return 0;
}
/* Create a lock filename that is a function of the RCS filename. */
1993-06-18 04:22:21 +00:00
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;
1993-06-18 04:22:21 +00:00
tp -= 2;
if (*tp == '_') {
error("RCS pathname `%s' ends with `%c'", RCSpath, *tp);
1993-06-18 04:22:21 +00:00
errno = EINVAL;
return 0;
}
*tp = '_';
}
sp = dirt->string;
1993-06-18 04:22:21 +00:00
f = 0;
/*
* good news:
* open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY)
* is atomic according to Posix 1003.1-1990.
1993-06-18 04:22:21 +00:00
* 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.
1993-06-18 04:22:21 +00:00
* bad news:
* If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't
* guarantee atomicity.
1993-06-18 04:22:21 +00:00
* 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,",
1993-06-18 04:22:21 +00:00
* 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
1993-06-18 04:22:21 +00:00
* 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.
1993-06-18 04:22:21 +00:00
* 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)
1993-06-18 04:22:21 +00:00
# else
# define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|OPEN_O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, OPEN_CREAT_READONLY)
1993-06-18 04:22:21 +00:00
# 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. */
1993-06-18 04:22:21 +00:00
e = errno;
setrid();
if (0 <= fdesc)
dirtpmaker[0] = effective;
if (fdescSafer < 0) {
if (e == EACCES && stat(sp,&statbuf) == 0)
1993-06-18 04:22:21 +00:00
/* The RCS file is busy. */
e = EEXIST;
} else {
e = ENOENT;
if (exists) {
f = Iopen(RCSpath, FOPEN_RB, status);
1993-06-18 04:22:21 +00:00
e = errno;
if (f && waslocked) {
1993-06-18 04:22:21 +00:00
/* Discard the previous lock in favor of this one. */
ORCSclose();
1993-06-18 04:22:21 +00:00
seteid();
r = un_link(lockname);
e = errno;
1993-06-18 04:22:21 +00:00
setrid();
if (r != 0)
enfaterror(e, lockname);
bufscpy(&dirtpname[lockdirtp_index], sp);
1993-06-18 04:22:21 +00:00
}
}
fdlock = fdescSafer;
1993-06-18 04:22:21 +00:00
}
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;
1993-06-18 04:22:21 +00:00
return;
}
faterror("keepdirtemp");
}
char const *
makedirtemp(isworkfile)
int isworkfile;
1993-06-18 04:22:21 +00:00
/*
* 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.
1993-06-18 04:22:21 +00:00
*/
{
register char *tp, *np;
register size_t dl;
register struct buf *bn;
register char const *name = isworkfile ? workname : RCSname;
1993-06-18 04:22:21 +00:00
dl = basefilename(name) - name;
bn = &dirtpname[newRCSdirtp_index + isworkfile];
1993-06-18 04:22:21 +00:00
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;
1993-06-18 04:22:21 +00:00
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
1993-06-18 04:22:21 +00:00
);
# 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,
1993-06-18 04:22:21 +00:00
* 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;
1993-06-18 04:22:21 +00:00
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) {
1993-06-18 04:22:21 +00:00
if (m == effective)
seteid();
VOID un_link(dirtpname[i].string);
1993-06-18 04:22:21 +00:00
if (m == effective)
setrid();
dirtpmaker[i] = notmade;
1993-06-18 04:22:21 +00:00
}
}
int
#if has_prototypes
chnamemod(
FILE **fromp, char const *from, char const *to,
int set_mode, mode_t mode, time_t mtime
)
1993-06-18 04:22:21 +00:00
/* 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;
1993-06-18 04:22:21 +00:00
#endif
/*
* Rename a file (with stream pointer *FROMP) from FROM to TO.
1993-06-18 04:22:21 +00:00
* FROM already exists.
* 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.
1993-06-18 04:22:21 +00:00
* 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 bad_a_rename || bad_NFS_rename
struct stat st;
if (bad_NFS_rename || (bad_a_rename && set_mode <= 0)) {
if (fstat(fileno(*fromp), &st) != 0)
return -1;
if (bad_a_rename && set_mode <= 0)
mode = st.st_mode;
}
# endif
1993-06-18 04:22:21 +00:00
# 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;
1993-06-18 04:22:21 +00:00
# endif
1993-06-18 04:22:21 +00:00
# if has_fchmod
if (0<set_mode && fchmod(fileno(*fromp),mode_while_renaming) == 0)
fchmod_set_mode = set_mode;
1993-06-18 04:22:21 +00:00
# endif
/* If bad_chmod_close, we must close before chmod. */
Ozclose(fromp);
if (fchmod_set_mode<set_mode && chmod(from, mode_while_renaming) != 0)
return -1;
if (setmtime(from, mtime) != 0)
1993-06-18 04:22:21 +00:00
return -1;
# 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;
1993-06-18 04:22:21 +00:00
# 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
1993-06-18 04:22:21 +00:00
# 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);
1993-06-18 04:22:21 +00:00
}
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;
1993-06-18 04:22:21 +00:00
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());
1993-06-18 04:22:21 +00:00
return 2;
}
found = trail;
}
if (!found)
return 0;
next = *found;
*target = next->delta;
if (delete) {
next->delta->lockedby = 0;
1993-06-18 04:22:21 +00:00
*found = next->nextlock;
}
return 1;
}
int
addlock(delta, verbose)
1993-06-18 04:22:21 +00:00
struct hshentry * delta;
int verbose;
1993-06-18 04:22:21 +00:00
/*
* 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
1993-06-18 04:22:21 +00:00
* DELTA is locked by somebody other than caller.
* Return 0 if the caller already holds the lock.
*/
{
register struct rcslock *next;
1993-06-18 04:22:21 +00:00
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
);
1993-06-18 04:22:21 +00:00
return -1;
}
next = ftalloc(struct rcslock);
1993-06-18 04:22:21 +00:00
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.
1993-06-18 04:22:21 +00:00
*/
{
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) {
1993-06-18 04:22:21 +00:00
next->num = num;
return 1;
1993-06-18 04:22:21 +00:00
} else {
rcserror("symbolic name %s already bound to %s",
1993-06-18 04:22:21 +00:00
name, next->num
);
return -1;
1993-06-18 04:22:21 +00:00
}
next = ftalloc(struct assoc);
next->symbol = name;
next->num = num;
next->nextassoc = Symbols;
Symbols = next;
return 1;
1993-06-18 04:22:21 +00:00
}
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());
1993-06-18 04:22:21 +00:00
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 0 on success, -1 on failure.
1993-06-18 04:22:21 +00:00
*/
{
int r = 0, e;
1993-06-18 04:22:21 +00:00
if (lockflag)
if (changed) {
if (changed < 0)
return -1;
putadmin();
1993-06-18 04:22:21 +00:00
puttree(Head, frewrite);
aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
foutptr = frewrite;
} else {
# if bad_creat0
int nr = !!frewrite, ne = 0;
# endif
ORCSclose();
1993-06-18 04:22:21 +00:00
seteid();
ignoreints();
# if bad_creat0
if (nr) {
nr = un_link(newRCSname);
ne = errno;
keepdirtemp(newRCSname);
}
# endif
r = un_link(lockname);
1993-06-18 04:22:21 +00:00
e = errno;
keepdirtemp(lockname);
1993-06-18 04:22:21 +00:00
restoreints();
setrid();
if (r != 0)
enerror(e, lockname);
# if bad_creat0
if (nr != 0) {
enerror(ne, newRCSname);
r = -1;
}
# endif
1993-06-18 04:22:21 +00:00
}
return r;
1993-06-18 04:22:21 +00:00
}
int
donerewrite(changed, newRCStime)
1993-06-18 04:22:21 +00:00
int changed;
time_t newRCStime;
1993-06-18 04:22:21 +00:00
/*
* 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.
1993-06-18 04:22:21 +00:00
*/
{
int r = 0, e = 0;
# if bad_creat0
int lr, le;
# endif
1993-06-18 04:22:21 +00:00
if (changed && !nerror) {
if (finptr) {
fastcopy(finptr, frewrite);
Izclose(&finptr);
}
if (1 < RCSstat.st_nlink)
rcswarn("breaking hard link");
aflush(frewrite);
1993-06-18 04:22:21 +00:00
seteid();
ignoreints();
r = chnamemod(
&frewrite, newRCSname, RCSname, changed,
RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH),
newRCStime
1993-06-18 04:22:21 +00:00
);
e = errno;
keepdirtemp(newRCSname);
# if bad_creat0
lr = un_link(lockname);
le = errno;
keepdirtemp(lockname);
# endif
1993-06-18 04:22:21 +00:00
restoreints();
setrid();
if (r != 0) {
enerror(e, RCSname);
error("saved in %s", newRCSname);
1993-06-18 04:22:21 +00:00
}
# if bad_creat0
if (lr != 0) {
enerror(le, lockname);
r = -1;
}
# endif
1993-06-18 04:22:21 +00:00
}
return r;
1993-06-18 04:22:21 +00:00
}
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.
*/
1993-06-18 04:22:21 +00:00
{
if (0 <= fdlock)
VOID close(fdlock);
if (frewrite)
/* Avoid fclose, since stdio may not be reentrant. */
VOID close(fileno(frewrite));
1993-06-18 04:22:21 +00:00
}