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