summaryrefslogtreecommitdiff
path: root/gnu/usr.bin/cvs
diff options
context:
space:
mode:
authorThorsten Lockert <tholo@cvs.openbsd.org>1998-03-12 07:22:51 +0000
committerThorsten Lockert <tholo@cvs.openbsd.org>1998-03-12 07:22:51 +0000
commit1bef13d3ddecb5ffa01042af01ece350c24f17a8 (patch)
tree904a1179d4a6e98574c9cce0e70139a89581c155 /gnu/usr.bin/cvs
parent32767bbf527af614ef42284ad2edb6aff5756f2a (diff)
Integrate local changes
Diffstat (limited to 'gnu/usr.bin/cvs')
-rw-r--r--gnu/usr.bin/cvs/configure13
-rw-r--r--gnu/usr.bin/cvs/configure.in8
-rw-r--r--gnu/usr.bin/cvs/contrib/Makefile.in2
-rw-r--r--gnu/usr.bin/cvs/lib/memmove.c57
-rw-r--r--gnu/usr.bin/cvs/src/Makefile.in8
-rw-r--r--gnu/usr.bin/cvs/src/checkout.c125
-rw-r--r--gnu/usr.bin/cvs/src/commit.c124
-rw-r--r--gnu/usr.bin/cvs/src/cvs.h8
-rw-r--r--gnu/usr.bin/cvs/src/lock.c12
-rw-r--r--gnu/usr.bin/cvs/src/main.c2
-rw-r--r--gnu/usr.bin/cvs/src/mkmodules.c9
-rw-r--r--gnu/usr.bin/cvs/src/parseinfo.c20
-rw-r--r--gnu/usr.bin/cvs/src/patch.c68
-rw-r--r--gnu/usr.bin/cvs/src/rcs.c2449
-rw-r--r--gnu/usr.bin/cvs/src/rcscmds.c63
-rw-r--r--gnu/usr.bin/cvs/src/server.c88
-rw-r--r--gnu/usr.bin/cvs/src/update.c650
17 files changed, 2863 insertions, 843 deletions
diff --git a/gnu/usr.bin/cvs/configure b/gnu/usr.bin/cvs/configure
index f2c3eabbcc8..dd3ce3fc14d 100644
--- a/gnu/usr.bin/cvs/configure
+++ b/gnu/usr.bin/cvs/configure
@@ -1856,7 +1856,7 @@ EOF
fi
-for ac_func in mkdir rename strstr dup2 strerror valloc waitpid vasprintf strtoul
+for ac_func in mkdir rename strstr dup2 strerror valloc waitpid memmove vasprintf strtoul
do
echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
echo "configure:1863: checking for $ac_func" >&5
@@ -3481,20 +3481,25 @@ EOF
fi
fi # enable_server
+cat >> confdefs.h <<\EOF
+#define PRESERVE_PERMISSIONS_SUPPORT 1
+EOF
+
+
echo $ac_n "checking for cygwin32""... $ac_c" 1>&6
-echo "configure:3486: checking for cygwin32" >&5
+echo "configure:3491: checking for cygwin32" >&5
if eval "test \"`echo '$''{'ccvs_cv_sys_cygwin32'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
-#line 3491 "configure"
+#line 3496 "configure"
#include "confdefs.h"
int main() {
return __CYGWIN32__;
; return 0; }
EOF
-if { (eval echo configure:3498: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+if { (eval echo configure:3503: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
rm -rf conftest*
ccvs_cv_sys_cygwin32=yes
else
diff --git a/gnu/usr.bin/cvs/configure.in b/gnu/usr.bin/cvs/configure.in
index 2296f71af59..cc678db8985 100644
--- a/gnu/usr.bin/cvs/configure.in
+++ b/gnu/usr.bin/cvs/configure.in
@@ -63,7 +63,7 @@ AC_TYPE_MODE_T
AC_TYPE_SIZE_T
AC_TYPE_PID_T
AC_STRUCT_ST_BLKSIZE
-AC_REPLACE_FUNCS(mkdir rename strstr dup2 strerror valloc waitpid vasprintf strtoul)
+AC_REPLACE_FUNCS(mkdir rename strstr dup2 strerror valloc waitpid memmove vasprintf strtoul)
AC_CHECK_FUNCS(fchmod fsync ftime mktemp putenv vprintf ftruncate timezone getpagesize initgroups fchdir sigaction sigprocmask sigvec sigsetmask sigblock tempnam tzset readlink wait3)
dnl
@@ -348,6 +348,12 @@ if test "$ac_cv_func_crypt" = yes; then
fi
fi # enable_server
+dnl For the moment we will assume that all systems which have
+dnl the unixyness to run configure are unixy enough to do the
+dnl PreservePermissions stuff. I have this sinking feeling that
+dnl things won't be that simple, before long.
+AC_DEFINE(PRESERVE_PERMISSIONS_SUPPORT)
+
dnl On cygwin32, we configure like a Unix system, but we use the
dnl Windows support code in lib/fncase.c to handle the case
dnl insensitive file system. We also need some support libraries. We
diff --git a/gnu/usr.bin/cvs/contrib/Makefile.in b/gnu/usr.bin/cvs/contrib/Makefile.in
index 8bd2408a90d..462ae89f65e 100644
--- a/gnu/usr.bin/cvs/contrib/Makefile.in
+++ b/gnu/usr.bin/cvs/contrib/Makefile.in
@@ -106,7 +106,7 @@ ls:
.PHONY: ls
clean:
- /bin/rm -f *.o core
+ rm -f *.o core
.PHONY: clean
distclean: clean
diff --git a/gnu/usr.bin/cvs/lib/memmove.c b/gnu/usr.bin/cvs/lib/memmove.c
new file mode 100644
index 00000000000..8818d46544c
--- /dev/null
+++ b/gnu/usr.bin/cvs/lib/memmove.c
@@ -0,0 +1,57 @@
+/* memmove -- copy memory regions of arbitary length
+ Copyright (C) 1991 Free Software Foundation, Inc.
+
+This file is part of the libiberty library.
+Libiberty is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+Libiberty 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with libiberty; see the file COPYING.LIB. If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA. */
+
+
+/*
+
+NAME
+
+ memmove -- copy memory regions of arbitary length
+
+SYNOPSIS
+
+ void memmove (void *out, const void *in, size_t n);
+
+DESCRIPTION
+
+ Copy LENGTH bytes from memory region pointed to by IN to memory
+ region pointed to by OUT.
+
+ Regions can be overlapping.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef __STDC__
+#include <stddef.h>
+#else
+#define size_t unsigned long
+#endif
+
+void *
+memmove (out, in, length)
+ void *out;
+ const void* in;
+ size_t length;
+{
+ bcopy(in, out, length);
+ return out;
+}
diff --git a/gnu/usr.bin/cvs/src/Makefile.in b/gnu/usr.bin/cvs/src/Makefile.in
index dfcd4d6e8d0..abfa957e279 100644
--- a/gnu/usr.bin/cvs/src/Makefile.in
+++ b/gnu/usr.bin/cvs/src/Makefile.in
@@ -39,7 +39,7 @@ LIBS = @LIBS@
SOURCES = add.c admin.c buffer.c checkin.c checkout.c classify.c client.c \
commit.c create_adm.c cvsrc.c diff.c edit.c entries.c error.c expand_path.c \
-fileattr.c find_names.c hash.c history.c ignore.c import.c \
+fileattr.c find_names.c hardlink.c hash.c history.c ignore.c import.c \
lock.c log.c login.c logmsg.c main.c mkmodules.c modules.c myndbm.c no_diff.c \
parseinfo.c patch.c rcs.c rcscmds.c recurse.c release.c remove.c repos.c \
root.c rtag.c scramble.c server.c status.c subr.c filesubr.c run.c \
@@ -47,14 +47,14 @@ tag.c update.c watch.c wrapper.c vers_ts.c version.c zlib.c
OBJECTS = add.o admin.o buffer.o checkin.o checkout.o classify.o client.o \
commit.o create_adm.o cvsrc.o diff.o edit.o entries.o expand_path.o \
-fileattr.o find_names.o hash.o history.o ignore.o import.o \
+fileattr.o find_names.o hardlink.o hash.o history.o ignore.o import.o \
lock.o log.o login.o logmsg.o main.o mkmodules.o modules.o myndbm.o no_diff.o \
parseinfo.o patch.o rcs.o rcscmds.o recurse.o release.o remove.o repos.o \
root.o rtag.o scramble.o server.o status.o tag.o update.o \
watch.o wrapper.o vers_ts.o \
subr.o filesubr.o run.o version.o error.o zlib.o
-HEADERS = buffer.h cvs.h rcs.h hash.h myndbm.h \
+HEADERS = buffer.h cvs.h rcs.h hardlink.h hash.h myndbm.h \
update.h server.h client.h error.h fileattr.h edit.h watch.h
TAGFILES = $(HEADERS) options.h.in $(SOURCES)
@@ -125,7 +125,7 @@ ls:
.PHONY: ls
clean:
- /bin/rm -f $(PROGS) *.o core check.log check.plog
+ rm -f $(PROGS) *.o core check.log check.plog
.PHONY: clean
distclean: clean
diff --git a/gnu/usr.bin/cvs/src/checkout.c b/gnu/usr.bin/cvs/src/checkout.c
index 3c1eef7fe11..46151eb9969 100644
--- a/gnu/usr.bin/cvs/src/checkout.c
+++ b/gnu/usr.bin/cvs/src/checkout.c
@@ -33,6 +33,7 @@
* edited by the user, if necessary (when the repository is moved, e.g.)
*/
+#include <assert.h>
#include "cvs.h"
static char *findslash PROTO((char *start, char *p));
@@ -431,7 +432,7 @@ struct dir_to_build
};
static int build_dirs_and_chdir PROTO ((struct dir_to_build *list,
- int sticky));
+ int sticky, int check_existing_dirs));
static void build_one_dir PROTO ((char *, char *, int));
@@ -580,7 +581,11 @@ checkout_proc (pargc, argv, where_orig, mwhere, mfile, shorten,
(void) strcat (where, "/");
}
- if (mwhere != NULL)
+ /* If the -d flag in the modules file specified an absolute
+ directory, let the user override it with the command-line
+ -d option. */
+
+ if ((mwhere != NULL) && (! isabsolute (mwhere)))
(void) strcat (where, mwhere);
else
(void) strcat (where, argv[0]);
@@ -723,19 +728,39 @@ internal error: %s doesn't start with %s in checkout_proc",
/* Make a copy of the repository name to play with. */
reposcopy = xstrdup (repository);
- /* FIXME: this should be written in terms of last_component instead
- of hardcoding '/'. This presumably affects OS/2, NT, &c, if
- the user specifies '\'. Likewise for the call to findslash. */
- cp = strrchr (where, '/');
- while (cp != NULL)
+ /* FIXME: this should be written in terms of last_component
+ instead of hardcoding '/'. This presumably affects OS/2,
+ NT, &c, if the user specifies '\'. Likewise for the call
+ to findslash. */
+ cp = where + strlen (where);
+ while (1)
{
struct dir_to_build *new;
+
+ cp = findslash (where, cp - 1);
+ if (cp == NULL)
+ break; /* we're done */
+
new = (struct dir_to_build *)
xmalloc (sizeof (struct dir_to_build));
new->dirpath = xmalloc (strlen (where));
- strncpy (new->dirpath, where, cp - where);
- new->dirpath[cp - where] = '\0';
+ /* If the user specified an absolute path for where, the
+ last path element we create should be the top-level
+ directory. */
+
+ if (cp - where)
+ {
+ strncpy (new->dirpath, where, cp - where);
+ new->dirpath[cp - where] = '\0';
+ }
+ else
+ {
+ /* where should always be at least one character long. */
+ assert (strlen (where));
+ strcpy (new->dirpath, "/");
+ }
+
/* Now figure out what repository directory to generate.
The most complete case would be something like this:
@@ -813,31 +838,41 @@ internal error: %s doesn't start with %s in checkout_proc",
new->next = head;
head = new;
-
- cp = findslash (where, cp - 1);
}
/* clean up */
free (reposcopy);
- /* The top-level CVSADM directory should always be
- CVSroot_directory. Create it.
-
- It may be argued that we shouldn't set any sticky bits for
- the top-level repository. FIXME? */
- build_one_dir (CVSroot_directory, ".", *pargc <= 1);
-
- /*
- * build dirs on the path if necessary and leave us in the bottom
- * directory (where if where was specified) doesn't contain a CVS
- * subdir yet, but all the others contain CVS and Entries.Static
- * files
- */
- if (build_dirs_and_chdir (head, *pargc <= 1) != 0)
{
- error (0, 0, "ignoring module %s", omodule);
- err = 1;
- goto out;
+ int where_is_absolute = isabsolute (where);
+
+ /* The top-level CVSADM directory should always be
+ CVSroot_directory. Create it, but only if WHERE is
+ relative. If WHERE is absolute, our current directory
+ may not have a thing to do with where the sources are
+ being checked out. If it does, build_dirs_and_chdir
+ will take care of creating adm files here. */
+
+ if (! where_is_absolute)
+ {
+ /* It may be argued that we shouldn't set any sticky
+ bits for the top-level repository. FIXME? */
+ build_one_dir (CVSroot_directory, ".", *pargc <= 1);
+ }
+
+
+ /* Build dirs on the path if necessary and leave us in the
+ bottom directory (where if where was specified) doesn't
+ contain a CVS subdir yet, but all the others contain
+ CVS and Entries.Static files */
+
+ if (build_dirs_and_chdir (head, *pargc <= 1,
+ where_is_absolute) != 0)
+ {
+ error (0, 0, "ignoring module %s", omodule);
+ err = 1;
+ goto out;
+ }
}
/* set up the repository (or make sure the old one matches) */
@@ -969,7 +1004,7 @@ internal error: %s doesn't start with %s in checkout_proc",
List *entries;
/* we are only doing files, so register them */
- entries = Entries_Open (0);
+ entries = Entries_Open (0, NULL);
for (i = 1; i < *pargc; i++)
{
char *line;
@@ -1066,13 +1101,22 @@ emptydir_name ()
return repository;
}
-/* Build all the dirs along the path to DIRS with CVS subdirs with appropriate
- repositories. If ->repository is NULL, do not create a CVSADM directory
- for that subdirectory; just CVS_CHDIR into it. */
+
+/* Build all the dirs along the path to DIRS with CVS subdirs with
+ appropriate repositories. If ->repository is NULL, do not create a
+ CVSADM directory for that subdirectory; just CVS_CHDIR into it. If
+ check_existing_dirs is nonzero, don't create directories if they
+ already exist, and don't try to write adm files in directories
+ where we don't have write permission. We use this last option
+ primarily when a user has specified an absolute path for checkout
+ -- we will often not have permission to top-level directories, so
+ we shouldn't complain. */
+
static int
-build_dirs_and_chdir (dirs, sticky)
+build_dirs_and_chdir (dirs, sticky, check_existing_dirs)
struct dir_to_build *dirs;
int sticky;
+ int check_existing_dirs;
{
int retval = 0;
struct dir_to_build *nextdir;
@@ -1080,20 +1124,31 @@ build_dirs_and_chdir (dirs, sticky)
while (dirs != NULL)
{
char *dir = last_component (dirs->dirpath);
+ int dir_is_writeable;
+
+ if ((! check_existing_dirs) || (! isdir (dir)))
+ mkdir_if_needed (dir);
- mkdir_if_needed (dir);
Subdir_Register (NULL, NULL, dir);
+
+ /* This is an expensive call -- only make it if necessary. */
+ if (check_existing_dirs)
+ dir_is_writeable = iswritable (dir);
+
if (CVS_CHDIR (dir) < 0)
{
error (0, errno, "cannot chdir to %s", dir);
retval = 1;
goto out;
}
- if (dirs->repository != NULL)
+
+ if ((dirs->repository != NULL)
+ && ((! check_existing_dirs) || dir_is_writeable))
{
build_one_dir (dirs->repository, dirs->dirpath, sticky);
free (dirs->repository);
}
+
nextdir = dirs->next;
free (dirs->dirpath);
free (dirs);
diff --git a/gnu/usr.bin/cvs/src/commit.c b/gnu/usr.bin/cvs/src/commit.c
index a7fec5f9bb8..4aa243868a9 100644
--- a/gnu/usr.bin/cvs/src/commit.c
+++ b/gnu/usr.bin/cvs/src/commit.c
@@ -19,6 +19,7 @@
#include "getline.h"
#include "edit.h"
#include "fileattr.h"
+#include "hardlink.h"
static Dtype check_direntproc PROTO ((void *callerdat, char *dir,
char *repos, char *update_dir,
@@ -81,7 +82,6 @@ static List *mulist;
static char *message;
static time_t last_register_time;
-
static const char *const commit_usage[] =
{
"Usage: %s %s [-nRlf] [-m msg | -F logfile] [-r rev] files...\n",
@@ -622,10 +622,23 @@ commit (argc, argv)
lock_tree_for_write (argc, argv, local, aflag);
/*
- * Set up the master update list
+ * Set up the master update list and hard link list
*/
mulist = getlist ();
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms)
+ {
+ hardlist = getlist ();
+
+ /*
+ * We need to save the working directory so that
+ * check_fileproc can construct a full pathname for each file.
+ */
+ working_dir = xgetwd();
+ }
+#endif
+
/*
* Run the recursion processor to verify the files are all up-to-date
*/
@@ -638,6 +651,17 @@ commit (argc, argv)
error (1, 0, "correct above errors first!");
}
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms)
+ {
+ /* hardlist now includes a complete index of the files
+ to be committed, indexed by inode. For each inode,
+ compile a list of the files that are linked to it,
+ and save this list in each file's hardlink_info node. */
+ (void) walklist (hardlist, cache_hardlinks_proc, NULL);
+ }
+#endif
+
/*
* Run the recursion processor to commit the files
*/
@@ -992,6 +1016,43 @@ warning: file `%s' seems to still contain conflict indicators",
ci->options = xstrdup(vers->options);
p->data = (char *) ci;
(void) addnode (cilist, p);
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms)
+ {
+ /* Add this file to hardlist, indexed on its inode. When
+ we are done, we can find out what files are hardlinked
+ to a given file by looking up its inode in hardlist. */
+ char *fullpath;
+ Node *linkp;
+ struct hardlink_info *hlinfo;
+
+ /* Get the full pathname of the current file. */
+ fullpath = xmalloc (strlen(working_dir) +
+ strlen(finfo->fullname) + 2);
+ sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
+
+ /* To permit following links in subdirectories, files
+ are keyed on finfo->fullname, not on finfo->name. */
+ linkp = lookup_file_by_inode (fullpath);
+
+ /* If linkp is NULL, the file doesn't exist... maybe
+ we're doing a remove operation? */
+ if (linkp != NULL)
+ {
+ /* Create a new hardlink_info node, which will record
+ the current file's status and the links listed in its
+ `hardlinks' delta field. We will append this
+ hardlink_info node to the appropriate hardlist entry. */
+ hlinfo = (struct hardlink_info *)
+ xmalloc (sizeof (struct hardlink_info));
+ hlinfo->status = status;
+ hlinfo->links = NULL;
+ linkp->data = (char *) hlinfo;
+ }
+ }
+#endif
+
break;
case T_UNKNOWN:
error (0, 0, "nothing known about `%s'", finfo->fullname);
@@ -1280,8 +1341,9 @@ commit_fileproc (callerdat, finfo)
/* Doesn't matter, it won't get checked. */
SERVER_UPDATED,
- (struct stat *) NULL,
- (unsigned char *) NULL);
+ (mode_t) -1,
+ (unsigned char *) NULL,
+ (struct buffer *) NULL);
}
#endif
}
@@ -1645,9 +1707,8 @@ remove_file (finfo, tag, message)
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
{
- if (!quiet)
- error (0, retcode == -1 ? errno : 0,
- "failed to check out `%s'", finfo->fullname);
+ error (0, 0,
+ "failed to check out `%s'", finfo->fullname);
return (1);
}
@@ -1981,13 +2042,21 @@ internal error: `%s' didn't move out of the attic",
if (tag && newfile)
{
char *tmp;
+ FILE *fp;
/* move the new file out of the way. */
fname = xmalloc (strlen (file) + sizeof (CVSADM)
+ sizeof (CVSPREFIX) + 10);
(void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
rename_file (file, fname);
- copy_file (DEVNULL, file);
+
+ /* Create empty FILE. Can't use copy_file with a DEVNULL
+ argument -- copy_file now ignores device files. */
+ fp = fopen (file, "w");
+ if (fp == NULL)
+ error (1, errno, "cannot open %s for writing", file);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", file);
tmp = xmalloc (strlen (file) + strlen (tag) + 80);
/* commit a dead revision. */
@@ -2156,18 +2225,31 @@ lock_RCS (user, rcs, rev, repository)
{
(void) RCS_lock(rcs, rev, 1);
}
- RCS_rewrite (rcs, NULL, NULL);
+
+ /* We used to call RCS_rewrite here, and that might seem
+ appropriate in order to write out the locked revision
+ information. However, such a call would actually serve no
+ purpose. CVS locks will prevent any interference from other
+ CVS processes. The comment above rcs_internal_lockfile
+ explains that it is already unsafe to use RCS and CVS
+ simultaneously. It follows that writing out the locked
+ revision information here would add no additional security.
+
+ If we ever do care about it, the proper fix is to create the
+ RCS lock file before calling this function, and maintain it
+ until the checkin is complete.
+
+ The call to RCS_lock is still required at present, since in
+ some cases RCS_checkin will determine which revision to check
+ in by looking for a lock. FIXME: This is rather roundabout,
+ and a more straightforward approach would probably be easier to
+ understand. */
if (err == 0)
{
if (sbranch != NULL)
free (sbranch);
- if (branch)
- {
- sbranch = branch;
- }
- else
- sbranch = NULL;
+ sbranch = branch;
return (0);
}
@@ -2182,7 +2264,8 @@ lock_RCS (user, rcs, rev, repository)
/* Called when "add"ing files to the RCS respository. It doesn't seem to
be possible to get RCS to use the right mode, so we change it after
- the fact. */
+ the fact. TODO: now that RCS has been librarified, we have the power
+ to change this. */
static void
fix_rcs_modes (rcs, user)
@@ -2192,6 +2275,12 @@ fix_rcs_modes (rcs, user)
struct stat sb;
mode_t rcs_mode;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Do ye nothing to the modes on a symbolic link. */
+ if (preserve_perms && islink (user))
+ return;
+#endif
+
if (CVS_STAT (user, &sb) < 0)
{
/* FIXME: Should be ->fullname. */
@@ -2201,6 +2290,9 @@ fix_rcs_modes (rcs, user)
/* Now we compute the new mode.
+ TODO: decide whether this whole thing can/should be skipped
+ when `preserve_perms' is set. Almost certainly so. -twp
+
The algorithm that we use is:
Write permission is always off (this is what RCS and CVS have always
diff --git a/gnu/usr.bin/cvs/src/cvs.h b/gnu/usr.bin/cvs/src/cvs.h
index a9e83599590..87f653598f3 100644
--- a/gnu/usr.bin/cvs/src/cvs.h
+++ b/gnu/usr.bin/cvs/src/cvs.h
@@ -406,6 +406,7 @@ int RCS_merge PROTO((RCSNode *, char *, char *, char *, char *, char *));
#define RCS_FLAGS_DEAD 2
#define RCS_FLAGS_QUIET 4
#define RCS_FLAGS_MODTIME 8
+#define RCS_FLAGS_KEEPFILE 16
extern int RCS_exec_rcsdiff PROTO ((RCSNode *rcsfile,
char *opts, char *options,
@@ -426,7 +427,7 @@ DBM *open_module PROTO((void));
FILE *open_file PROTO((const char *, const char *));
List *Find_Directories PROTO((char *repository, int which, List *entries));
void Entries_Close PROTO((List *entries));
-List *Entries_Open PROTO((int aflag));
+List *Entries_Open PROTO ((int aflag, char *update_dir));
void Subdirs_Known PROTO((List *entries));
void Subdir_Register PROTO((List *, const char *, const char *));
void Subdir_Deregister PROTO((List *, const char *, const char *));
@@ -465,10 +466,12 @@ int SIG_register PROTO((int sig, SIGCLEANUPPROC sigcleanup));
int isdir PROTO((const char *file));
int isfile PROTO((const char *file));
int islink PROTO((const char *file));
+int isdevice PROTO ((const char *));
int isreadable PROTO((const char *file));
int iswritable PROTO((const char *file));
int isaccessible PROTO((const char *file, const int mode));
int isabsolute PROTO((const char *filename));
+char *xreadlink PROTO((const char *link));
char *last_component PROTO((char *path));
char *get_homedir PROTO ((void));
char *cvs_temp_name PROTO ((void));
@@ -731,6 +734,9 @@ void freevers_ts PROTO ((Vers_TS ** versp));
int Checkin PROTO ((int type, struct file_info *finfo, char *rcs, char *rev,
char *tag, char *options, char *message));
int No_Difference PROTO ((struct file_info *finfo, Vers_TS *vers));
+/* TODO: can the finfo argument to special_file_mismatch be changed? -twp */
+int special_file_mismatch PROTO ((struct file_info *finfo,
+ char *rev1, char *rev2));
/* CVSADM_BASEREV stuff, from entries.c. */
extern char *base_get PROTO ((struct file_info *));
diff --git a/gnu/usr.bin/cvs/src/lock.c b/gnu/usr.bin/cvs/src/lock.c
index 72ae48a5a7a..e6a6be359c6 100644
--- a/gnu/usr.bin/cvs/src/lock.c
+++ b/gnu/usr.bin/cvs/src/lock.c
@@ -704,7 +704,6 @@ lock_obtained (repos)
static int lock_filesdoneproc PROTO ((void *callerdat, int err,
char *repository, char *update_dir,
List *entries));
-static int fsortcmp PROTO((const Node * p, const Node * q));
/*
* Create a list of repositories to lock
@@ -733,17 +732,6 @@ lock_filesdoneproc (callerdat, err, repository, update_dir, entries)
return (err);
}
-/*
- * compare two lock list nodes (for sort)
- */
-static int
-fsortcmp (p, q)
- const Node *p;
- const Node *q;
-{
- return (strcmp (p->key, q->key));
-}
-
void
lock_tree_for_write (argc, argv, local, aflag)
int argc;
diff --git a/gnu/usr.bin/cvs/src/main.c b/gnu/usr.bin/cvs/src/main.c
index 7e111bed2b6..3f90e6973b7 100644
--- a/gnu/usr.bin/cvs/src/main.c
+++ b/gnu/usr.bin/cvs/src/main.c
@@ -528,7 +528,7 @@ main (argc, argv)
(void) fputs (config_string, stdout);
(void) fputs ("\n", stdout);
(void) fputs ("\
-Copyright (c) 1989-1997 Brian Berliner, david d `zoo' zuhn, \n\
+Copyright (c) 1989-1998 Brian Berliner, david d `zoo' zuhn, \n\
Jeff Polk, and other authors\n", stdout);
(void) fputs ("\n", stdout);
(void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout);
diff --git a/gnu/usr.bin/cvs/src/mkmodules.c b/gnu/usr.bin/cvs/src/mkmodules.c
index bb95d592532..2ec62d2aff3 100644
--- a/gnu/usr.bin/cvs/src/mkmodules.c
+++ b/gnu/usr.bin/cvs/src/mkmodules.c
@@ -279,10 +279,17 @@ static const char *const modules_contents[] = {
static const char *const config_contents[] = {
"# Set this to \"no\" if pserver shouldn't check system users/passwords\n",
"#SystemAuth=no\n",
+ "\n",
+ "# Set `PreservePermissions' to `yes' to save file status information\n",
+ "# in the repository.\n",
+ "#PreservePermissions=no\n",
+ "\n",
"# Set this to the name of a local tag to use in addition to Id\n",
"#tag=OurTag\n",
+ "\n",
"# Set this to the default umask to use when creating files and directories\n",
"#umask=002\n",
+ "\n",
"# Set this to the default data resource limit to use\n",
"#dlimit=65536\n",
NULL
@@ -544,7 +551,7 @@ checkout_file (file, temp)
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
{
- error (0, retcode == -1 ? errno : 0, "failed to check out %s file",
+ error (0, 0, "failed to check out %s file",
file);
}
freercsnode (&rcsnode);
diff --git a/gnu/usr.bin/cvs/src/parseinfo.c b/gnu/usr.bin/cvs/src/parseinfo.c
index cb3c0ee3b02..6ae074e4e4f 100644
--- a/gnu/usr.bin/cvs/src/parseinfo.c
+++ b/gnu/usr.bin/cvs/src/parseinfo.c
@@ -350,6 +350,26 @@ parse_config (cvsroot)
}
#endif /* BSD */
}
+ else if (strcmp (line, "PreservePermissions") == 0)
+ {
+ if (strcmp (p, "no") == 0)
+ preserve_perms = 0;
+ else if (strcmp (p, "yes") == 0)
+ {
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ preserve_perms = 1;
+#else
+ error (0, 0, "\
+warning: this CVS does not support PreservePermissions");
+#endif
+ }
+ else
+ {
+ error (0, 0, "unrecognized value '%s' for PreservePermissions",
+ p);
+ goto error_return;
+ }
+ }
else
{
/* We may be dealing with a keyword which was added in a
diff --git a/gnu/usr.bin/cvs/src/patch.c b/gnu/usr.bin/cvs/src/patch.c
index 9d091c34b6f..9f56b627bfa 100644
--- a/gnu/usr.bin/cvs/src/patch.c
+++ b/gnu/usr.bin/cvs/src/patch.c
@@ -499,23 +499,43 @@ patch_fileproc (callerdat, finfo)
ret = 0;
goto out2;
}
+
+ /* Create 3 empty files. I'm not really sure there is any advantage
+ to doing so now rather than just waiting until later. */
tmpfile1 = cvs_temp_name ();
- if ((fp1 = CVS_FOPEN (tmpfile1, "w+")) != NULL)
- (void) fclose (fp1);
+ fp1 = CVS_FOPEN (tmpfile1, "w+");
+ if (fp1 == NULL)
+ {
+ error (0, errno, "cannot create temporary file %s", tmpfile1);
+ ret = 1;
+ goto out;
+ }
+ else
+ if (fclose (fp1) < 0)
+ error (0, errno, "warning: cannot close %s", tmpfile1);
tmpfile2 = cvs_temp_name ();
- if ((fp2 = CVS_FOPEN (tmpfile2, "w+")) != NULL)
- (void) fclose (fp2);
+ fp2 = CVS_FOPEN (tmpfile2, "w+");
+ if (fp2 == NULL)
+ {
+ error (0, errno, "cannot create temporary file %s", tmpfile2);
+ ret = 1;
+ goto out;
+ }
+ else
+ if (fclose (fp2) < 0)
+ error (0, errno, "warning: cannot close %s", tmpfile2);
tmpfile3 = cvs_temp_name ();
- if ((fp3 = CVS_FOPEN (tmpfile3, "w+")) != NULL)
- (void) fclose (fp3);
- if (fp1 == NULL || fp2 == NULL || fp3 == NULL)
+ fp3 = CVS_FOPEN (tmpfile3, "w+");
+ if (fp3 == NULL)
{
- /* FIXME: should be printing a proper error message, with errno-based
- message, and the filename which we could not create. */
- error (0, 0, "cannot create temporary files");
+ error (0, errno, "cannot create temporary file %s", tmpfile3);
ret = 1;
goto out;
}
+ else
+ if (fclose (fp3) < 0)
+ error (0, errno, "warning: cannot close %s", tmpfile3);
+
if (vers_tag != NULL)
{
retcode = RCS_checkout (rcsfile, (char *) NULL, vers_tag,
@@ -523,9 +543,8 @@ patch_fileproc (callerdat, finfo)
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
{
- if (!really_quiet)
- error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
- "co of revision %s in %s failed", vers_tag, rcs);
+ error (0, 0,
+ "cannot check out revision %s of %s", vers_tag, rcs);
ret = 1;
goto out;
}
@@ -548,9 +567,8 @@ patch_fileproc (callerdat, finfo)
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
{
- if (!really_quiet)
- error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
- "co of revision %s in %s failed", vers_head, rcs);
+ error (0, 0,
+ "cannot check out revision %s of %s", vers_head, rcs);
ret = 1;
goto out;
}
@@ -584,10 +602,16 @@ patch_fileproc (callerdat, finfo)
if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
getline (&line2, &line2_chars_allocated, fp) < 0)
{
- error (0, errno, "failed to read diff file header %s for %s",
- tmpfile3, rcs);
+ if (feof (fp))
+ error (0, 0, "\
+failed to read diff file header %s for %s: end of file", tmpfile3, rcs);
+ else
+ error (0, errno,
+ "failed to read diff file header %s for %s",
+ tmpfile3, rcs);
ret = 1;
- (void) fclose (fp);
+ if (fclose (fp) < 0)
+ error (0, errno, "error closing %s", tmpfile3);
goto out;
}
if (!unidiff)
@@ -599,7 +623,8 @@ patch_fileproc (callerdat, finfo)
{
error (0, 0, "invalid diff header for %s", rcs);
ret = 1;
- (void) fclose (fp);
+ if (fclose (fp) < 0)
+ error (0, errno, "error closing %s", tmpfile3);
goto out;
}
}
@@ -612,7 +637,8 @@ patch_fileproc (callerdat, finfo)
{
error (0, 0, "invalid unidiff header for %s", rcs);
ret = 1;
- (void) fclose (fp);
+ if (fclose (fp) < 0)
+ error (0, errno, "error closing %s", tmpfile3);
goto out;
}
}
diff --git a/gnu/usr.bin/cvs/src/rcs.c b/gnu/usr.bin/cvs/src/rcs.c
index 23d7e97b8e1..29461b69d3f 100644
--- a/gnu/usr.bin/cvs/src/rcs.c
+++ b/gnu/usr.bin/cvs/src/rcs.c
@@ -11,6 +11,9 @@
#include <assert.h>
#include "cvs.h"
#include "edit.h"
+#include "hardlink.h"
+
+int preserve_perms = 0;
/* The RCS -k options, and a set of enums that must match the array.
These come first so that we can use enum kflag in function
@@ -19,11 +22,57 @@ static const char *const kflags[] =
{"kv", "kvl", "k", "v", "o", "b", (char *) NULL};
enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B };
+/* A structure we use to buffer the contents of an RCS file. The
+ various fields are only referenced directly by the rcsbuf_*
+ functions. We declare the struct here so that we can allocate it
+ on the stack, rather than in memory. */
+
+struct rcsbuffer
+{
+ /* Points to the current position in the buffer. */
+ char *ptr;
+ /* Points just after the last valid character in the buffer. */
+ char *ptrend;
+ /* The file. */
+ FILE *fp;
+ /* The name of the file, used for error messages. */
+ const char *filename;
+ /* The starting file position of the data in the buffer. */
+ unsigned long pos;
+ /* The length of the value. */
+ size_t vlen;
+ /* Whether the value contains an '@' string. If so, we can not
+ compress whitespace characters. */
+ int at_string;
+ /* The number of embedded '@' characters in an '@' string. If
+ this is non-zero, we must search the string for pairs of '@'
+ and convert them to a single '@'. */
+ int embedded_at;
+};
+
static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile));
static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch));
-static int getrcskey PROTO((FILE * fp, char **keyp, char **valp,
- size_t *lenp));
-static void getrcsrev PROTO ((FILE *fp, char **revp));
+static void rcsbuf_open PROTO ((struct rcsbuffer *, FILE *fp,
+ const char *filename, unsigned long pos));
+static void rcsbuf_close PROTO ((struct rcsbuffer *));
+static int rcsbuf_getkey PROTO ((struct rcsbuffer *, char **keyp,
+ char **valp));
+static int rcsbuf_getrevnum PROTO ((struct rcsbuffer *, char **revp));
+static char *rcsbuf_fill PROTO ((struct rcsbuffer *, char *ptr, char **keyp,
+ char **valp));
+static char *rcsbuf_valcopy PROTO ((struct rcsbuffer *, char *val, int polish,
+ size_t *lenp));
+static void rcsbuf_valpolish PROTO ((struct rcsbuffer *, char *val, int polish,
+ size_t *lenp));
+static void rcsbuf_valpolish_internal PROTO ((struct rcsbuffer *, char *to,
+ const char *from, size_t *lenp));
+static unsigned long rcsbuf_ftell PROTO ((struct rcsbuffer *));
+static void rcsbuf_get_buffered PROTO ((struct rcsbuffer *, char **datap,
+ size_t *lenp));
+static void rcsbuf_cache PROTO ((RCSNode *, struct rcsbuffer *));
+static void rcsbuf_cache_close PROTO ((void));
+static void rcsbuf_cache_open PROTO ((RCSNode *, long, FILE **,
+ struct rcsbuffer *));
static int checkmagic_proc PROTO((Node *p, void *closure));
static void do_branches PROTO((List * list, char *val));
static void do_symbols PROTO((List * list, char *val));
@@ -40,12 +89,15 @@ static void expand_keywords PROTO((RCSNode *, RCSVers *, const char *,
static void cmp_file_buffer PROTO((void *, const char *, size_t));
enum rcs_delta_op {RCS_ANNOTATE, RCS_FETCH};
-static void RCS_deltas PROTO ((RCSNode *, FILE *, char *, enum rcs_delta_op,
- char **, size_t *, char **, size_t *));
+static void RCS_deltas PROTO ((RCSNode *, FILE *, struct rcsbuffer *, char *,
+ enum rcs_delta_op, char **, size_t *,
+ char **, size_t *));
/* Routines for reading, parsing and writing RCS files. */
-static RCSVers *getdelta PROTO ((FILE *, char *));
-static Deltatext *RCS_getdeltatext PROTO ((RCSNode *, FILE *));
+static RCSVers *getdelta PROTO ((struct rcsbuffer *, char *, char **,
+ char **));
+static Deltatext *RCS_getdeltatext PROTO ((RCSNode *, FILE *,
+ struct rcsbuffer *));
static void freedeltatext PROTO ((Deltatext *));
static void RCS_putadmin PROTO ((RCSNode *, FILE *));
@@ -54,13 +106,21 @@ static void RCS_putdesc PROTO ((RCSNode *, FILE *));
static void putdelta PROTO ((RCSVers *, FILE *));
static int putrcsfield_proc PROTO ((Node *, void *));
static int putsymbol_proc PROTO ((Node *, void *));
-static void RCS_copydeltas PROTO ((RCSNode *, FILE *, FILE *, Deltatext *, char *));
+static void RCS_copydeltas PROTO ((RCSNode *, FILE *, struct rcsbuffer *,
+ FILE *, Deltatext *, char *));
+static int count_delta_actions PROTO ((Node *, void *));
static void putdeltatext PROTO ((FILE *, Deltatext *));
static FILE *rcs_internal_lockfile PROTO ((char *));
static void rcs_internal_unlockfile PROTO ((FILE *, char *));
static char *rcs_lockfilename PROTO ((char *));
+/* The RCS file reading functions are called a lot, and they do some
+ string comparisons. This macro speeds things up a bit by skipping
+ the function call when the first characters are different. It
+ evaluates its arguments multiple times. */
+#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp ((a), (b)) == 0)
+
/*
* We don't want to use isspace() from the C library because:
*
@@ -90,7 +150,6 @@ static const char spacetab[] = {
#define whitespace(c) (spacetab[(unsigned char)c] != 0)
-
/* Parse an rcsfile given a user file name and a repository. If there is
an error, we print an error message and return NULL. If the file
does not exist, we return NULL without printing anything (I'm not
@@ -106,6 +165,10 @@ RCS_parse (file, repos)
RCSNode *retval;
char *rcsfile;
+ /* We're creating a new RCSNode, so there is no hope of finding it
+ in the cache. */
+ rcsbuf_cache_close ();
+
rcsfile = xmalloc (strlen (repos) + strlen (file)
+ sizeof (RCSEXT) + sizeof (CVSATTIC) + 10);
(void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
@@ -115,7 +178,6 @@ RCS_parse (file, repos)
if (rcs != NULL)
rcs->flags |= VALID;
- fclose (fp);
retval = rcs;
goto out;
}
@@ -136,7 +198,6 @@ RCS_parse (file, repos)
rcs->flags |= VALID;
}
- fclose (fp);
retval = rcs;
goto out;
}
@@ -165,7 +226,6 @@ RCS_parse (file, repos)
if (rcs != NULL)
rcs->flags |= VALID;
- fclose (fp);
free (rcs->path);
rcs->path = found_path;
retval = rcs;
@@ -189,7 +249,6 @@ RCS_parse (file, repos)
rcs->flags |= VALID;
}
- fclose (fp);
free (rcs->path);
rcs->path = found_path;
retval = rcs;
@@ -221,6 +280,10 @@ RCS_parsercsfile (rcsfile)
FILE *fp;
RCSNode *rcs;
+ /* We're creating a new RCSNode, so there is no hope of finding it
+ in the cache. */
+ rcsbuf_cache_close ();
+
/* open the rcsfile */
if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL)
{
@@ -230,7 +293,6 @@ RCS_parsercsfile (rcsfile)
rcs = RCS_parsercsfile_i (fp, rcsfile);
- fclose (fp);
return (rcs);
}
@@ -243,6 +305,7 @@ RCS_parsercsfile_i (fp, rcsfile)
const char *rcsfile;
{
RCSNode *rdata;
+ struct rcsbuffer rcsbuf;
char *key, *value;
/* make a node */
@@ -251,40 +314,32 @@ RCS_parsercsfile_i (fp, rcsfile)
rdata->refcount = 1;
rdata->path = xstrdup (rcsfile);
- /* Process HEAD and BRANCH keywords from the RCS header.
+ /* Process HEAD, BRANCH, and EXPAND keywords from the RCS header.
Most cvs operations on the main branch don't need any more
information. Those that do call RCS_reparsercsfile to parse
- the rest of the header and the deltas.
-
- People often wonder whether this is inefficient, to open the
- file once here and once in RCS_reparsercsfile. Well, it might
- help a little bit if we kept the file open (I haven't tried
- timing this myself), but basically the common case, which we
- want to optimize, is the one in which we call
- RCS_parsercsfile_i and not RCS_reparsercsfile (for example,
- "cvs update" on a lot of files most of which are unmodified).
- So making the case in which we call RCS_reparsercsfile fast is
- not as important. */
-
- if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL)
+ the rest of the header and the deltas. */
+
+ rcsbuf_open (&rcsbuf, fp, rcsfile, 0);
+
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
goto l_error;
- if (strcmp (key, RCSDESC) == 0)
+ if (STREQ (key, RCSDESC))
goto l_error;
- if (strcmp (RCSHEAD, key) == 0 && value != NULL)
- rdata->head = xstrdup (value);
+ if (STREQ (RCSHEAD, key) && value != NULL)
+ rdata->head = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL);
- if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL)
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
goto l_error;
- if (strcmp (key, RCSDESC) == 0)
+ if (STREQ (key, RCSDESC))
goto l_error;
- if (strcmp (RCSBRANCH, key) == 0 && value != NULL)
+ if (STREQ (RCSBRANCH, key) && value != NULL)
{
char *cp;
- rdata->branch = xstrdup (value);
+ rdata->branch = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL);
if ((numdots (rdata->branch) & 1) != 0)
{
/* turn it into a branch if it's a revision */
@@ -293,23 +348,43 @@ RCS_parsercsfile_i (fp, rcsfile)
}
}
- rdata->flags |= PARTIAL;
- return rdata;
-
-l_error:
- if (!really_quiet)
+ /* Look ahead for expand, stopping when we see desc or a revision
+ number. */
+ while (1)
{
- if (ferror(fp))
- {
- error (1, 0, "error reading `%s'", rcsfile);
- }
- else
+ char *cp;
+
+ if (STREQ (RCSEXPAND, key))
{
- error (0, 0, "`%s' does not appear to be a valid rcs file",
- rcsfile);
+ rdata->expand = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
+ break;
}
+
+ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
+ /* do nothing */ ;
+ if (*cp == '\0')
+ break;
+
+ if (STREQ (RCSDESC, key))
+ break;
+
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+ break;
}
+
+ rdata->flags |= PARTIAL;
+
+ rcsbuf_cache (rdata, &rcsbuf);
+
+ return rdata;
+
+l_error:
+ error (0, 0, "`%s' does not appear to be a valid rcs file",
+ rcsfile);
+ rcsbuf_close (&rcsbuf);
freercsnode (&rdata);
+ fclose (fp);
return (NULL);
}
@@ -321,25 +396,24 @@ l_error:
If PFP is NULL, close the file when done. Otherwise, leave it open
and store the FILE * in *PFP. */
void
-RCS_reparsercsfile (rdata, pfp)
+RCS_reparsercsfile (rdata, pfp, rcsbufp)
RCSNode *rdata;
FILE **pfp;
+ struct rcsbuffer *rcsbufp;
{
FILE *fp;
char *rcsfile;
-
+ struct rcsbuffer rcsbuf;
Node *q, *kv;
RCSVers *vnode;
- long fpos;
+ int gotkey;
char *cp;
char *key, *value;
assert (rdata != NULL);
rcsfile = rdata->path;
- fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ);
- if (fp == NULL)
- error (1, 0, "unable to reopen `%s'", rcsfile);
+ rcsbuf_cache_open (rdata, 0, &fp, &rcsbuf);
/* make a node */
/* This probably shouldn't be done until later: if a file has an
@@ -351,66 +425,68 @@ RCS_reparsercsfile (rdata, pfp)
* process all the special header information, break out when we get to
* the first revision delta
*/
+ gotkey = 0;
for (;;)
{
- fpos = ftell (fp);
-
/* get the next key/value pair */
-
- /* if key is NULL here, then the file is missing some headers
- or we had trouble reading the file. */
- if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL)
+ if (!gotkey)
{
- if (ferror(fp))
- {
- error (1, 0, "error reading `%s'", rcsfile);
- }
- else
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
{
error (1, 0, "`%s' does not appear to be a valid rcs file",
rcsfile);
}
}
- /* Skip head and branch tags; we already have them. */
- if (strcmp (key, RCSHEAD) == 0 || strcmp (key, RCSBRANCH) == 0)
+ gotkey = 0;
+
+ /* Skip head, branch and expand tags; we already have them. */
+ if (STREQ (key, RCSHEAD)
+ || STREQ (key, RCSBRANCH)
+ || STREQ (key, RCSEXPAND))
+ {
continue;
+ }
- if (strcmp (key, "access") == 0)
+ if (STREQ (key, "access"))
{
if (value != NULL)
- rdata->access = xstrdup (value);
+ {
+ /* We pass the POLISH parameter as 1 because
+ RCS_addaccess expects nothing but spaces. FIXME:
+ It would be easy and more efficient to change
+ RCS_addaccess. */
+ rdata->access = rcsbuf_valcopy (&rcsbuf, value, 1,
+ (size_t *) NULL);
+ }
continue;
}
/* We always save lock information, so that we can handle
-kkvl correctly when checking out a file. */
- if (strcmp (key, "locks") == 0)
+ if (STREQ (key, "locks"))
{
if (value != NULL)
- rdata->locks_data = xstrdup (value);
- fpos = ftell (fp);
- if (getrcskey (fp, &key, &value, NULL) >= 0 &&
- strcmp (key, "strict") == 0 &&
- value == NULL)
+ rdata->locks_data = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+ {
+ error (1, 0, "premature end of file reading %s", rcsfile);
+ }
+ if (STREQ (key, "strict") && value == NULL)
{
rdata->strict_locks = 1;
}
else
- (void) fseek (fp, fpos, SEEK_SET);
+ gotkey = 1;
continue;
}
- if (strcmp (RCSSYMBOLS, key) == 0)
+ if (STREQ (RCSSYMBOLS, key))
{
if (value != NULL)
- rdata->symbols_data = xstrdup(value);
- continue;
- }
-
- if (strcmp (RCSEXPAND, key) == 0)
- {
- rdata->expand = xstrdup (value);
+ rdata->symbols_data = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
continue;
}
@@ -421,15 +497,19 @@ RCS_reparsercsfile (rdata, pfp)
*/
for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
/* do nothing */ ;
- if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
+ /* Note that when comparing with RCSDATE, we are not massaging
+ VALUE from the string found in the RCS file. This is OK
+ since we know exactly what to expect. */
+ if (*cp == '\0' && strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) == 0)
break;
- if (strcmp (key, RCSDESC) == 0)
+ if (STREQ (key, RCSDESC))
break;
- if (strcmp (key, "comment") == 0)
+ if (STREQ (key, "comment"))
{
- rdata->comment = xstrdup (value);
+ rdata->comment = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
continue;
}
if (rdata->other == NULL)
@@ -437,7 +517,7 @@ RCS_reparsercsfile (rdata, pfp)
kv = getnode ();
kv->type = RCSFIELD;
kv->key = xstrdup (key);
- kv->data = xstrdup (value);
+ kv->data = rcsbuf_valcopy (&rcsbuf, value, 1, (size_t *) NULL);
if (addnode (rdata->other, kv) != 0)
{
error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
@@ -448,15 +528,11 @@ RCS_reparsercsfile (rdata, pfp)
/* if we haven't grabbed it yet, we didn't want it */
}
- /*
- * we got out of the loop, so we have the first part of the first
- * revision delta in our hand key=the revision and value=the date key and
- * its value
- */
- /* First, seek back to the start of the delta block. */
- (void) fseek (fp, fpos, SEEK_SET);
+ /* We got out of the loop, so we have the first part of the first
+ revision delta in KEY (the revision) and VALUE (the date key
+ and its value). This is what getdelta expects to receive. */
- while ((vnode = getdelta (fp, rcsfile)) != NULL)
+ while ((vnode = getdelta (&rcsbuf, rcsfile, &key, &value)) != NULL)
{
/* get the node */
q = getnode ();
@@ -476,8 +552,9 @@ RCS_reparsercsfile (rdata, pfp)
}
}
- (void) getrcskey (fp, &key, &value, NULL);
- if (key != NULL && strcmp (key, RCSDESC) == 0)
+ /* Here KEY and VALUE are whatever caused getdelta to return NULL. */
+
+ if (STREQ (key, RCSDESC))
{
if (rdata->desc != NULL)
{
@@ -486,19 +563,17 @@ RCS_reparsercsfile (rdata, pfp)
key, rcsfile);
free (rdata->desc);
}
- rdata->desc = xstrdup (value);
+ rdata->desc = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL);
}
- rdata->delta_pos = ftell (fp);
+ rdata->delta_pos = rcsbuf_ftell (&rcsbuf);
if (pfp == NULL)
- {
- if (fclose (fp) < 0)
- error (0, errno, "cannot close %s", rcsfile);
- }
+ rcsbuf_cache (rdata, &rcsbuf);
else
{
*pfp = fp;
+ *rcsbufp = rcsbuf;
}
rdata->flags &= ~PARTIAL;
}
@@ -520,31 +595,21 @@ RCS_fully_parse (rcs)
RCSNode *rcs;
{
FILE *fp;
+ struct rcsbuffer rcsbuf;
- RCS_reparsercsfile (rcs, &fp);
+ RCS_reparsercsfile (rcs, &fp, &rcsbuf);
while (1)
{
- int c;
char *key, *value;
- size_t vallen;
Node *vers;
RCSVers *vnode;
/* Rather than try to keep track of how much information we
have read, just read to the end of the file. */
- do
- {
- c = getc (fp);
- if (c == EOF)
- break;
- } while (whitespace (c));
- if (c == EOF)
+ if (! rcsbuf_getrevnum (&rcsbuf, &key))
break;
- if (ungetc (c, fp) == EOF)
- error (1, errno, "ungetc failed");
- getrcsrev (fp, &key);
vers = findnode (rcs->versions, key);
if (vers == NULL)
error (1, 0,
@@ -553,9 +618,9 @@ RCS_fully_parse (rcs)
vnode = (RCSVers *) vers->data;
- while (getrcskey (fp, &key, &value, &vallen) >= 0)
+ while (rcsbuf_getkey (&rcsbuf, &key, &value))
{
- if (strcmp (key, "text") != 0)
+ if (! STREQ (key, "text"))
{
Node *kv;
@@ -564,7 +629,7 @@ RCS_fully_parse (rcs)
kv = getnode ();
kv->type = RCSFIELD;
kv->key = xstrdup (key);
- kv->data = xstrdup (value);
+ kv->data = rcsbuf_valcopy (&rcsbuf, value, 1, (size_t *) NULL);
if (addnode (vnode->other, kv) != 0)
{
error (0, 0,
@@ -577,7 +642,7 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'",
continue;
}
- if (strcmp (vnode->version, rcs->head) != 0)
+ if (! STREQ (vnode->version, rcs->head))
{
unsigned long add, del;
char buf[50];
@@ -589,8 +654,10 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'",
del = 0;
if (value != NULL)
{
+ size_t vallen;
const char *cp;
+ rcsbuf_valpolish (&rcsbuf, value, 0, &vallen);
cp = value;
while (cp < value + vallen)
{
@@ -670,8 +737,7 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'",
}
}
- if (fclose (fp) < 0)
- error (0, errno, "cannot close %s", rcs->path);
+ rcsbuf_cache (rcs, &rcsbuf);
}
/*
@@ -766,353 +832,877 @@ rcsvers_delproc (p)
{
free_rcsvers_contents ((RCSVers *) p->data);
}
+
+/* These functions retrieve keys and values from an RCS file using a
+ buffer. We use this somewhat complex approach because it turns out
+ that for many common operations, CVS spends most of its time
+ reading keys, so it's worth doing some fairly hairy optimization. */
-/*
- * getrcskey - fill in the key and value from the rcs file the algorithm is
- * as follows
- *
- * o skip whitespace
- * o fill in key with everything up to next white
- * space or semicolon
- * o if key == "desc" then key and data are NULL and return -1
- * o if key wasn't terminated by a semicolon, skip white space and fill
- * in value with everything up to a semicolon
- * o compress all whitespace down to a single space
- * o if a word starts with @, do funky rcs processing
- * o strip whitespace off end of value or set value to NULL if it empty
- * o return 0 since we found something besides "desc"
- *
- * Sets *KEYP and *VALUEP to point to storage managed by the getrcskey
- * function; the contents are only valid until the next call to
- * getrcskey or getrcsrev. If LENP is not NULL, this sets *LENP to
- * the length of *VALUEP; this is needed if the string might contain
- * binary data.
- */
+/* The number of bytes we try to read each time we need more data. */
-static char *key = NULL;
-static char *value = NULL;
-static size_t keysize = 0;
-static size_t valsize = 0;
+#define RCSBUF_BUFSIZE (8192)
-static int
-getrcskey (fp, keyp, valp, lenp)
+/* The buffer we use to store data. This grows as needed. */
+
+static char *rcsbuf_buffer = NULL;
+static size_t rcsbuf_buffer_size = 0;
+
+/* Whether rcsbuf_buffer is in use. This is used as a sanity check. */
+
+static int rcsbuf_inuse;
+
+/* Set up to start gathering keys and values from an RCS file. This
+ initializes RCSBUF. */
+
+static void
+rcsbuf_open (rcsbuf, fp, filename, pos)
+ struct rcsbuffer *rcsbuf;
FILE *fp;
+ const char *filename;
+ unsigned long pos;
+{
+ if (rcsbuf_inuse)
+ error (1, 0, "rcsbuf_open: internal error");
+ rcsbuf_inuse = 1;
+
+ if (rcsbuf_buffer_size < RCSBUF_BUFSIZE)
+ expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, RCSBUF_BUFSIZE);
+
+ rcsbuf->ptr = rcsbuf_buffer;
+ rcsbuf->ptrend = rcsbuf_buffer;
+ rcsbuf->fp = fp;
+ rcsbuf->filename = filename;
+ rcsbuf->pos = pos;
+ rcsbuf->vlen = 0;
+ rcsbuf->at_string = 0;
+ rcsbuf->embedded_at = 0;
+}
+
+/* Stop gathering keys from an RCS file. */
+
+static void
+rcsbuf_close (rcsbuf)
+ struct rcsbuffer *rcsbuf;
+{
+ if (! rcsbuf_inuse)
+ error (1, 0, "rcsbuf_close: internal error");
+ rcsbuf_inuse = 0;
+}
+
+/* Read a key/value pair from an RCS file. This sets *KEYP to point
+ to the key, and *VALUEP to point to the value. A missing or empty
+ value is indicated by setting *VALUEP to NULL.
+
+ This function returns 1 on success, or 0 on EOF. If there is an
+ error reading the file, or an EOF in an unexpected location, it
+ gives a fatal error.
+
+ This sets *KEYP and *VALUEP to point to storage managed by
+ rcsbuf_getkey. Moreover, *VALUEP has not been massaged from the
+ RCS format: it may contain embedded whitespace and embedded '@'
+ characters. Call rcsbuf_valcopy or rcsbuf_valpolish to do
+ appropriate massaging. */
+
+static int
+rcsbuf_getkey (rcsbuf, keyp, valp)
+ struct rcsbuffer *rcsbuf;
char **keyp;
char **valp;
- size_t *lenp;
{
- char *cur, *max;
- int c;
- int just_string;
+ register const char * const my_spacetab = spacetab;
+ register char *ptr, *ptrend;
+ char c;
- if (lenp != NULL)
- *lenp = 0;
+#define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0)
- /* skip leading whitespace */
- do
+ rcsbuf->vlen = 0;
+ rcsbuf->at_string = 0;
+ rcsbuf->embedded_at = 0;
+
+ ptr = rcsbuf->ptr;
+ ptrend = rcsbuf->ptrend;
+
+ /* Sanity check. */
+ if (ptr < rcsbuf_buffer || ptr > rcsbuf_buffer + rcsbuf_buffer_size)
+ abort ();
+
+ /* If the pointer is more than RCSBUF_BUFSIZE bytes into the
+ buffer, move back to the start of the buffer. This keeps the
+ buffer from growing indefinitely. */
+ if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE)
{
- c = getc (fp);
- if (c == EOF)
- {
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
- }
- } while (whitespace (c));
+ int len;
+
+ len = ptrend - ptr;
- /* fill in key */
- cur = key;
- max = key + keysize;
- while (!whitespace (c) && c != ';')
+ /* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes
+ at a time, so we can't have more bytes than that past PTR. */
+ if (len > RCSBUF_BUFSIZE)
+ abort ();
+
+ /* Update the POS field, which holds the file offset of the
+ first byte in the RCSBUF_BUFFER buffer. */
+ rcsbuf->pos += ptr - rcsbuf_buffer;
+
+ memcpy (rcsbuf_buffer, ptr, len);
+ ptr = rcsbuf_buffer;
+ ptrend = ptr + len;
+ rcsbuf->ptrend = ptrend;
+ }
+
+ /* Skip leading whitespace. */
+
+ while (1)
{
- if (cur >= max)
+ if (ptr >= ptrend)
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL);
+ if (ptr == NULL)
+ return 0;
+ ptrend = rcsbuf->ptrend;
}
- *cur++ = c;
- c = getc (fp);
- if (c == EOF)
+ c = *ptr;
+ if (! my_whitespace (c))
+ break;
+
+ ++ptr;
+ }
+
+ /* We've found the start of the key. */
+
+ *keyp = ptr;
+
+ if (c != ';')
+ {
+ while (1)
{
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
+ ++ptr;
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0, "EOF in key in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+ c = *ptr;
+ if (c == ';' || my_whitespace (c))
+ break;
}
}
- if (cur >= max)
+
+ /* Here *KEYP points to the key in the buffer, C is the character
+ we found at the of the key, and PTR points to the location in
+ the buffer where we found C. We must set *PTR to \0 in order
+ to terminate the key. If the key ended with ';', then there is
+ no value. */
+
+ *ptr = '\0';
+ ++ptr;
+
+ if (c == ';')
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ *valp = NULL;
+ rcsbuf->ptr = ptr;
+ return 1;
}
- *cur = '\0';
- /* skip whitespace between key and val */
- while (whitespace (c))
+ /* C must be whitespace. Skip whitespace between the key and the
+ value. If we find ';' now, there is no value. */
+
+ while (1)
{
- c = getc (fp);
- if (c == EOF)
+ if (ptr >= ptrend)
{
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0, "EOF while looking for value in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
}
- }
+ c = *ptr;
+ if (c == ';')
+ {
+ *valp = NULL;
+ rcsbuf->ptr = ptr + 1;
+ return 1;
+ }
+ if (! my_whitespace (c))
+ break;
+ ++ptr;
+ }
- /* if we ended key with a semicolon, there is no value */
- if (c == ';')
+ /* Now PTR points to the start of the value, and C is the first
+ character of the value. */
+
+ if (c != '@')
+ *valp = ptr;
+ else
{
- *keyp = key;
- *valp = (char *) NULL;
- return (0);
- }
+ char *pat;
+ size_t vlen;
- /* otherwise, there might be a value, so fill it in */
- cur = value;
- max = value + valsize;
+ /* Optimize the common case of a value composed of a single
+ '@' string. */
- just_string = (strcmp (key, RCSDESC) == 0
- || strcmp (key, "text") == 0
- || strcmp (key, "log") == 0);
+ rcsbuf->at_string = 1;
- /* process the value */
- for (;;)
- {
- /* handle RCS "strings" */
- if (c == '@')
+ ++ptr;
+
+ *valp = ptr;
+
+ while (1)
{
- for (;;)
+ while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
{
- c = getc (fp);
- if (c == EOF)
- {
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
- }
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuf_fill, so that we will wind up setting PTR to
+ the location corresponding to the old PTREND, so
+ that we don't search the same bytes again. */
+ ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0,
+ "EOF while looking for end of string in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
- if (c == '@')
+ /* Handle the special case of an '@' right at the end of
+ the known bytes. */
+ if (pat + 1 >= ptrend)
+ {
+ /* Note that we pass PAT, not PTR, here. */
+ pat = rcsbuf_fill (rcsbuf, pat, keyp, valp);
+ if (pat == NULL)
{
- c = getc (fp);
- if (c == EOF)
- {
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
- }
-
- if (c != '@')
- break;
- }
+ /* EOF here is OK; it just means that the last
+ character of the file was an '@' terminating a
+ value for a key type which does not require a
+ trailing ';'. */
+ pat = rcsbuf->ptrend - 1;
- if (cur >= max)
- {
- size_t curoff = cur - value;
- expand_string (&value, &valsize, valsize + 1);
- cur = value + curoff;
- max = value + valsize;
}
- *cur++ = c;
+ ptrend = rcsbuf->ptrend;
+
+ /* Note that the value of PTR is bogus here. This is
+ OK, because we don't use it. */
}
+
+ if (pat + 1 >= ptrend || pat[1] != '@')
+ break;
+
+ /* We found an '@' pair in the string. Keep looking. */
+ ++rcsbuf->embedded_at;
+ ptr = pat + 2;
}
- /* The syntax for some key-value pairs is different; they
- don't end with a semicolon. */
- if (just_string)
- break;
+ /* Here PAT points to the final '@' in the string. */
+
+ *pat = '\0';
+
+ vlen = pat - *valp;
+ if (vlen == 0)
+ *valp = NULL;
+ rcsbuf->vlen = vlen;
+
+ ptr = pat + 1;
+ }
+
+ /* Certain keywords only have a '@' string. If there is no '@'
+ string, then the old getrcskey function assumed that they had
+ no value, and we do the same. */
- /* compress whitespace down to a single space */
- if (whitespace (c))
+ {
+ char *k;
+
+ k = *keyp;
+ if (STREQ (k, RCSDESC)
+ || STREQ (k, "text")
+ || STREQ (k, "log"))
{
- do {
- c = getc (fp);
- if (c == EOF)
- {
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
- }
- } while (whitespace (c));
+ if (c != '@')
+ *valp = NULL;
+ rcsbuf->ptr = ptr;
+ return 1;
+ }
+ }
- /* Do not include any trailing whitespace in the value. */
- if (c != ';')
+ /* If we've already gathered a '@' string, try to skip whitespace
+ and find a ';'. */
+ if (c == '@')
+ {
+ while (1)
+ {
+ char n;
+
+ if (ptr >= ptrend)
{
- if (cur >= max)
- {
- size_t curoff = cur - value;
- expand_string (&value, &valsize, valsize + 1);
- cur = value + curoff;
- max = value + valsize;
- }
- *cur++ = ' ';
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0, "EOF in value in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+ n = *ptr;
+ if (n == ';')
+ {
+ /* We're done. We already set everything up for this
+ case above. */
+ rcsbuf->ptr = ptr + 1;
+ return 1;
}
+ if (! my_whitespace (n))
+ break;
+ ++ptr;
}
- /* if we got a semi-colon we are done with the entire value */
- if (c == ';')
- break;
+ /* The value extends past the '@' string. We need to undo the
+ closing of the '@' done in the default case above. This
+ case never happens in a plain RCS file, but it can happen
+ if user defined phrases are used. */
+ if (rcsbuf->vlen != 0)
+ (*valp)[rcsbuf->vlen] = ' ';
+ else
+ *valp = ptr;
+ }
- if (cur >= max)
+ /* Here we have a value which is not a simple '@' string. We need
+ to gather up everything until the next ';', including any '@'
+ strings. *VALP points to the start of the value. If
+ RCSBUF->VLEN is not zero, then we have already read an '@'
+ string, and PTR points to the data following the '@' string.
+ Otherwise, PTR points to the start of the value. */
+
+ while (1)
+ {
+ char *start, *psemi, *pat;
+
+ /* Find the ';' which must end the value. */
+ start = ptr;
+ while ((psemi = memchr (ptr, ';', ptrend - ptr)) == NULL)
{
- size_t curoff = cur - value;
- expand_string (&value, &valsize, valsize + 1);
- cur = value + curoff;
- max = value + valsize;
+ int slen;
+
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuf_fill, so that we will wind up setting PTR to the
+ location corresponding to the old PTREND, so that we
+ don't search the same bytes again. */
+ slen = start - *valp;
+ ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0, "EOF in value in RCS file %s", rcsbuf->filename);
+ start = *valp + slen;
+ ptrend = rcsbuf->ptrend;
}
- *cur++ = c;
- c = getc (fp);
- if (c == EOF)
+ /* See if there are any '@' strings in the value. */
+ pat = memchr (start, '@', psemi - start);
+
+ if (pat == NULL)
{
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
+ size_t vlen;
+
+ /* We're done with the value. Trim any trailing
+ whitespace. */
+
+ rcsbuf->ptr = psemi + 1;
+
+ start = *valp;
+ while (psemi > start && my_whitespace (psemi[-1]))
+ --psemi;
+ *psemi = '\0';
+
+ vlen = psemi - start;
+ if (vlen == 0)
+ *valp = NULL;
+ rcsbuf->vlen = vlen;
+
+ return 1;
}
+
+ /* We found an '@' string in the value. We set
+ RCSBUF->AT_STRING, which means that we won't be able to
+ compress whitespace correctly for this type of value.
+ Since this type of value never arises in a normal RCS file,
+ this should not be a big deal. It means that if anybody
+ adds a phrase which can have both an '@' string and regular
+ text, they will have to handle whitespace compression
+ themselves. */
+
+ rcsbuf->at_string = 1;
+
+ *pat = ' ';
+
+ ptr = pat + 1;
+
+ while (1)
+ {
+ while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
+ {
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuff_fill, so that we will wind up setting PTR
+ to the location corresponding to the old PTREND, so
+ that we don't search the same bytes again. */
+ ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0,
+ "EOF while looking for end of string in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ /* Handle the special case of an '@' right at the end of
+ the known bytes. */
+ if (pat + 1 >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0, "EOF in value in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ if (pat[1] != '@')
+ break;
+
+ /* We found an '@' pair in the string. Keep looking. */
+ ++rcsbuf->embedded_at;
+ ptr = pat + 2;
+ }
+
+ /* Here PAT points to the final '@' in the string. */
+
+ *pat = ' ';
+
+ ptr = pat + 1;
}
- /* terminate the string */
- if (cur >= max)
+#undef my_whitespace
+}
+
+/* Read an RCS revision number from an RCS file. This sets *REVP to
+ point to the revision number; it will point to space that is
+ managed by the rcsbuf functions, and is only good until the next
+ call to rcsbuf_getkey or rcsbuf_getrevnum.
+
+ This function returns 1 on success, or 0 on EOF. If there is an
+ error reading the file, or an EOF in an unexpected location, it
+ gives a fatal error. */
+
+static int
+rcsbuf_getrevnum (rcsbuf, revp)
+ struct rcsbuffer *rcsbuf;
+ char **revp;
+{
+ char *ptr, *ptrend;
+ char c;
+
+ ptr = rcsbuf->ptr;
+ ptrend = rcsbuf->ptrend;
+
+ *revp = NULL;
+
+ /* Skip leading whitespace. */
+
+ while (1)
{
- size_t curoff = cur - value;
- expand_string (&value, &valsize, valsize + 1);
- cur = value + curoff;
- max = value + valsize;
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL);
+ if (ptr == NULL)
+ return 0;
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ if (! whitespace (c))
+ break;
+
+ ++ptr;
}
- *cur = '\0';
- /* if the string is empty, make it null */
- if (value && cur != value)
+ if (! isdigit (c) && c != '.')
+ error (1, 0,
+ "unexpected `%c' reading revision number in RCS file %s",
+ c, rcsbuf->filename);
+
+ *revp = ptr;
+
+ do
+ {
+ ++ptr;
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, revp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0,
+ "unexpected EOF reading revision number in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ }
+ while (isdigit (c) || c == '.');
+
+ if (! whitespace (c))
+ error (1, 0, "unexpected `%c' reading revision number in RCS file %s",
+ c, rcsbuf->filename);
+
+ *ptr = '\0';
+
+ rcsbuf->ptr = ptr + 1;
+
+ return 1;
+}
+
+/* Fill RCSBUF_BUFFER with bytes from the file associated with RCSBUF,
+ updating PTR and the PTREND field. If KEYP and *KEYP are not NULL,
+ then *KEYP points into the buffer, and must be adjusted if the
+ buffer is changed. Likewise for VALP. Returns the new value of
+ PTR, or NULL on error. */
+
+static char *
+rcsbuf_fill (rcsbuf, ptr, keyp, valp)
+ struct rcsbuffer *rcsbuf;
+ char *ptr;
+ char **keyp;
+ char **valp;
+{
+ int got;
+
+ if (rcsbuf->ptrend - rcsbuf_buffer + RCSBUF_BUFSIZE > rcsbuf_buffer_size)
+ {
+ int poff, peoff, koff, voff;
+
+ poff = ptr - rcsbuf_buffer;
+ peoff = rcsbuf->ptrend - rcsbuf_buffer;
+ if (keyp != NULL && *keyp != NULL)
+ koff = *keyp - rcsbuf_buffer;
+ if (valp != NULL && *valp != NULL)
+ voff = *valp - rcsbuf_buffer;
+
+ expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size,
+ rcsbuf_buffer_size + RCSBUF_BUFSIZE);
+
+ ptr = rcsbuf_buffer + poff;
+ rcsbuf->ptrend = rcsbuf_buffer + peoff;
+ if (keyp != NULL && *keyp != NULL)
+ *keyp = rcsbuf_buffer + koff;
+ if (valp != NULL && *valp != NULL)
+ *valp = rcsbuf_buffer + voff;
+ }
+
+ got = fread (rcsbuf->ptrend, 1, RCSBUF_BUFSIZE, rcsbuf->fp);
+ if (got == 0)
+ {
+ if (ferror (rcsbuf->fp))
+ error (1, errno, "cannot read %s", rcsbuf->filename);
+ return NULL;
+ }
+
+ rcsbuf->ptrend += got;
+
+ return ptr;
+}
+
+/* Copy the value VAL returned by rcsbuf_getkey into a memory buffer,
+ returning the memory buffer. Polish the value like
+ rcsbuf_valpolish, q.v. */
+
+static char *
+rcsbuf_valcopy (rcsbuf, val, polish, lenp)
+ struct rcsbuffer *rcsbuf;
+ char *val;
+ int polish;
+ size_t *lenp;
+{
+ size_t vlen;
+ int embedded_at;
+ char *ret;
+
+ if (val == NULL)
{
- *valp = value;
if (lenp != NULL)
- *lenp = cur - value;
+ *lenp = 0;
+ return NULL;
}
- else
- *valp = NULL;
- *keyp = key;
- return (0);
+
+ vlen = rcsbuf->vlen;
+ embedded_at = rcsbuf->embedded_at;
+
+ ret = xmalloc (vlen - embedded_at + 1);
+
+ if (rcsbuf->at_string ? embedded_at == 0 : ! polish)
+ {
+ /* No special action to take. */
+ memcpy (ret, val, vlen + 1);
+ if (lenp != NULL)
+ *lenp = vlen;
+ return ret;
+ }
+
+ rcsbuf_valpolish_internal (rcsbuf, ret, val, lenp);
+ return ret;
}
-/* Read an RCS revision number from FP. Put a pointer to it in *REVP;
- it points to space managed by getrcsrev which is only good until
- the next call to getrcskey or getrcsrev. */
+/* Polish the value VAL returned by rcsbuf_getkey. The POLISH
+ parameter is non-zero if multiple embedded whitespace characters
+ should be compressed into a single whitespace character. Note that
+ leading and trailing whitespace was already removed by
+ rcsbuf_getkey. Within an '@' string, pairs of '@' characters are
+ compressed into a single '@' character regardless of the value of
+ POLISH. If LENP is not NULL, set *LENP to the length of the value. */
+
static void
-getrcsrev (fp, revp)
- FILE *fp;
- char **revp;
+rcsbuf_valpolish (rcsbuf, val, polish, lenp)
+ struct rcsbuffer *rcsbuf;
+ char *val;
+ int polish;
+ size_t *lenp;
{
- char *cur;
- char *max;
- int c;
+ if (val == NULL)
+ {
+ if (lenp != NULL)
+ *lenp= 0;
+ return;
+ }
- do {
- c = getc (fp);
- if (c == EOF)
+ if (rcsbuf->at_string ? rcsbuf->embedded_at == 0 : ! polish)
+ {
+ /* No special action to take. */
+ if (lenp != NULL)
+ *lenp = rcsbuf->vlen;
+ return;
+ }
+
+ rcsbuf_valpolish_internal (rcsbuf, val, val, lenp);
+}
+
+/* Internal polishing routine, called from rcsbuf_valcopy and
+ rcsbuf_valpolish. */
+
+static void
+rcsbuf_valpolish_internal (rcsbuf, to, from, lenp)
+ struct rcsbuffer *rcsbuf;
+ char *to;
+ const char *from;
+ size_t *lenp;
+{
+ size_t len;
+
+ len = rcsbuf->vlen;
+
+ if (! rcsbuf->at_string)
+ {
+ char *orig_to;
+ size_t clen;
+
+ orig_to = to;
+
+ for (clen = len; clen > 0; ++from, --clen)
{
- /* FIXME: should be including filename in error message. */
- if (ferror (fp))
- error (1, errno, "cannot read rcs file");
- else
- error (1, 0, "unexpected end of file reading rcs file");
+ char c;
+
+ c = *from;
+ if (whitespace (c))
+ {
+ /* Note that we know that clen can not drop to zero
+ while we have whitespace, because we know there is
+ no trailing whitespace. */
+ while (whitespace (from[1]))
+ {
+ ++from;
+ --clen;
+ }
+ c = ' ';
+ }
+ *to++ = c;
}
- } while (whitespace (c));
- if (!(isdigit (c) || c == '.'))
- /* FIXME: should be including filename in error message. */
- error (1, 0, "error reading rcs file; revision number expected");
+ *to = '\0';
- cur = key;
- max = key + keysize;
- while (isdigit (c) || c == '.')
+ if (lenp != NULL)
+ *lenp = to - orig_to;
+ }
+ else
{
- if (cur >= max)
+ const char *orig_from;
+ char *orig_to;
+ int embedded_at;
+ size_t clen;
+
+ orig_from = from;
+ orig_to = to;
+
+ embedded_at = rcsbuf->embedded_at;
+
+ if (lenp != NULL)
+ *lenp = len - embedded_at;
+
+ for (clen = len; clen > 0; ++from, --clen)
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ char c;
+
+ c = *from;
+ *to++ = c;
+ if (c == '@')
+ {
+ ++from;
+
+ /* Sanity check. */
+ if (*from != '@' || clen == 0)
+ abort ();
+
+ --clen;
+
+ --embedded_at;
+ if (embedded_at == 0)
+ {
+ /* We've found all the embedded '@' characters.
+ We can just memcpy the rest of the buffer after
+ this '@' character. */
+ if (orig_to != orig_from)
+ memcpy (to, from + 1, clen - 1);
+ else
+ memmove (to, from + 1, clen - 1);
+ from += clen;
+ to += clen - 1;
+ break;
+ }
+ }
}
- *cur++ = c;
- c = getc (fp);
- if (c == EOF)
+ /* Sanity check. */
+ if (from != orig_from + len
+ || to != orig_to + (len - rcsbuf->embedded_at))
{
- /* FIXME: should be including filename in error message. */
- if (ferror (fp))
- error (1, errno, "cannot read rcs file");
- else
- error (1, 0, "unexpected end of file reading rcs file");
+ abort ();
}
- }
- if (cur >= max)
- {
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ *to = '\0';
}
- *cur = '\0';
- *revp = key;
}
-/* Like getrcsrev, but don't die on error. Return the last character
- read (last call to getc, which may be EOF). TODO: implement getrcsrev
- in terms of this function. */
-static int
-getrevnum (fp, revp)
- FILE *fp;
- char **revp;
+/* Return the current position of an rcsbuf. */
+
+static unsigned long
+rcsbuf_ftell (rcsbuf)
+ struct rcsbuffer *rcsbuf;
{
- char *cur;
- char *max;
- int c;
+ return rcsbuf->pos + (rcsbuf->ptr - rcsbuf_buffer);
+}
- *revp = NULL;
- do {
- c = getc (fp);
- if (c == EOF)
- return c;
- } while (whitespace (c));
+/* Return a pointer to any data buffered for RCSBUF, along with the
+ length. */
+
+static void
+rcsbuf_get_buffered (rcsbuf, datap, lenp)
+ struct rcsbuffer *rcsbuf;
+ char **datap;
+ size_t *lenp;
+{
+ *datap = rcsbuf->ptr;
+ *lenp = rcsbuf->ptrend - rcsbuf->ptr;
+}
- if (!(isdigit (c) || c == '.'))
- return c;
+/* CVS optimizes by quickly reading some header information from a
+ file. If it decides it needs to do more with the file, it reopens
+ it. We speed that up here by maintaining a cache of a single open
+ file, to save the time it takes to reopen the file in the common
+ case. */
- cur = key;
- max = key + keysize;
- while (isdigit (c) || c == '.')
+static RCSNode *cached_rcs;
+static struct rcsbuffer cached_rcsbuf;
+
+/* Cache RCS and RCSBUF. This takes responsibility for closing
+ RCSBUF->FP. */
+
+static void
+rcsbuf_cache (rcs, rcsbuf)
+ RCSNode *rcs;
+ struct rcsbuffer *rcsbuf;
+{
+ if (cached_rcs != NULL)
+ rcsbuf_cache_close ();
+ cached_rcs = rcs;
+ ++rcs->refcount;
+ cached_rcsbuf = *rcsbuf;
+}
+
+/* If there is anything in the cache, close it. */
+
+static void
+rcsbuf_cache_close ()
+{
+ if (cached_rcs != NULL)
{
- if (cur >= max)
+ if (fclose (cached_rcsbuf.fp) != 0)
+ error (0, errno, "cannot close %s", cached_rcsbuf.filename);
+ rcsbuf_close (&cached_rcsbuf);
+ freercsnode (&cached_rcs);
+ cached_rcs = NULL;
+ }
+}
+
+/* Open an rcsbuffer for RCS, getting it from the cache if possible.
+ Set *FPP to the file, and *RCSBUFP to the rcsbuf. The file should
+ be put at position POS. */
+
+static void
+rcsbuf_cache_open (rcs, pos, pfp, prcsbuf)
+ RCSNode *rcs;
+ long pos;
+ FILE **pfp;
+ struct rcsbuffer *prcsbuf;
+{
+ if (cached_rcs == rcs)
+ {
+ if (rcsbuf_ftell (&cached_rcsbuf) != pos)
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ if (fseek (cached_rcsbuf.fp, pos, SEEK_SET) != 0)
+ error (1, 0, "cannot fseek RCS file %s",
+ cached_rcsbuf.filename);
+ cached_rcsbuf.ptr = rcsbuf_buffer;
+ cached_rcsbuf.ptrend = rcsbuf_buffer;
+ cached_rcsbuf.pos = pos;
}
- *cur = c;
+ *pfp = cached_rcsbuf.fp;
- c = getc (fp);
- if (c == EOF)
- break;
- cur++;
- }
+ /* When RCS_parse opens a file using fopen_case, it frees the
+ filename which we cached in CACHED_RCSBUF and stores a new
+ file name in RCS->PATH. We avoid problems here by always
+ copying the filename over. FIXME: This is hackish. */
+ cached_rcsbuf.filename = rcs->path;
+
+ *prcsbuf = cached_rcsbuf;
+
+ cached_rcs = NULL;
- if (cur >= max)
+ /* Removing RCS from the cache removes a reference to it. */
+ --rcs->refcount;
+ if (rcs->refcount <= 0)
+ error (1, 0, "rcsbuf_cache_open: internal error");
+ }
+ else
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ if (cached_rcs != NULL)
+ rcsbuf_cache_close ();
+
+ *pfp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
+ if (*pfp == NULL)
+ error (1, 0, "unable to reopen `%s'", rcs->path);
+ if (pos != 0)
+ {
+ if (fseek (*pfp, pos, SEEK_SET) != 0)
+ error (1, 0, "cannot fseek RCS file %s", rcs->path);
+ }
+ rcsbuf_open (prcsbuf, *pfp, rcs->path, pos);
}
- *cur = '\0';
- *revp = key;
- return c;
}
+
/*
* process the symbols list of the rcs file
*/
@@ -1312,10 +1902,10 @@ RCS_gettag (rcs, symtag, force_tag_match, simple_tag)
/* XXX this is probably not necessary, --jtc */
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* If tag is "HEAD", special case to get head RCS revision */
- if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0'))
+ if (tag && (STREQ (tag, TAG_HEAD) || *tag == '\0'))
#if 0 /* This #if 0 is only in the Cygnus code. Why? Death support? */
if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC))
return ((char *) NULL); /* head request for removed file */
@@ -1501,7 +2091,7 @@ checkmagic_proc (p, closure)
Node *p;
void *closure;
{
- if (strcmp (check_rev, p->data) == 0)
+ if (STREQ (check_rev, p->data))
return (1);
else
return (0);
@@ -1655,7 +2245,7 @@ RCS_getbranch (rcs, tag, force_tag_match)
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* find out if the tag contains a dot, or is on the trunk */
cp = strrchr (tag, '.');
@@ -1873,7 +2463,7 @@ RCS_getdate (rcs, date, force_tag_match)
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* if the head is on a branch, try the branch first */
if (rcs->branch != NULL)
@@ -1911,7 +2501,7 @@ RCS_getdate (rcs, date, force_tag_match)
*/
/* if we found what we're looking for, and it's not 1.1 return it */
- if (cur_rev != NULL && strcmp (cur_rev, "1.1") != 0)
+ if (cur_rev != NULL && ! STREQ (cur_rev, "1.1"))
return (xstrdup (cur_rev));
/* look on the vendor branch */
@@ -1960,7 +2550,7 @@ RCS_getdatebranch (rcs, date, branch)
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
p = findnode (rcs->versions, xrev);
free (xrev);
@@ -2057,7 +2647,7 @@ RCS_getrevtime (rcs, rev, date, fudge)
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* look up the revision */
p = findnode (rcs->versions, rev);
@@ -2109,7 +2699,7 @@ RCS_getlocks (rcs)
assert(rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->locks_data) {
rcs->locks = getlist ();
@@ -2128,7 +2718,7 @@ RCS_symbols(rcs)
assert(rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->symbols_data) {
rcs->symbols = getlist ();
@@ -2150,7 +2740,7 @@ translate_symtag (rcs, tag)
const char *tag;
{
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->symbols != NULL)
{
@@ -2234,7 +2824,7 @@ RCS_check_kflag (arg)
{
for (cpp = kflags; *cpp != NULL; cpp++)
{
- if (strcmp (arg, *cpp) == 0)
+ if (STREQ (arg, *cpp))
break;
}
}
@@ -2292,7 +2882,7 @@ RCS_isdead (rcs, tag)
RCSVers *version;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
p = findnode (rcs->versions, tag);
if (p == NULL)
@@ -2312,8 +2902,6 @@ RCS_getexpand (rcs)
RCSNode *rcs;
{
assert (rcs != NULL);
- if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
return rcs->expand;
}
@@ -2910,7 +3498,16 @@ expand_keywords (rcs, ver, name, log, loglen, expand, buf, len, retbuf, retlen)
OPTIONS is a string such as "-kb" or "-kv" for keyword expansion
options. It may be NULL to use the default expansion mode of the
- file, typically "-kkv". */
+ file, typically "-kkv".
+
+ On an error which prevented checking out the file, either print a
+ nonfatal error and return 1, or give a fatal error. On success,
+ return 0. */
+
+/* This function mimics the behavior of `rcs co' almost exactly. The
+ chief difference is in its support for preserving file ownership,
+ permissions, and special files across checkin and checkout -- see
+ comments in RCS_checkin for some issues about this. -twp */
int
RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
@@ -2925,15 +3522,27 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
{
int free_rev = 0;
enum kflag expand;
- FILE *fp;
+ FILE *fp, *ofp;
struct stat sb;
+ struct rcsbuffer rcsbuf;
char *key;
char *value;
size_t len;
int free_value = 0;
char *log = NULL;
size_t loglen;
- FILE *ofp;
+ Node *vp = NULL;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ uid_t rcs_owner;
+ gid_t rcs_group;
+ mode_t rcs_mode;
+ int change_rcs_owner = 0;
+ int change_rcs_group = 0;
+ int change_rcs_mode = 0;
+ int special_file = 0;
+ unsigned long devnum_long;
+ dev_t devnum = 0;
+#endif
if (trace)
{
@@ -2970,34 +3579,25 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
free_rev = 1;
}
- if (rev == NULL || strcmp (rev, rcs->head) == 0)
+ if (rev == NULL || STREQ (rev, rcs->head))
{
int gothead;
/* We want the head revision. Try to read it directly. */
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, &fp);
+ RCS_reparsercsfile (rcs, &fp, &rcsbuf);
else
- {
- fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
- if (fp == NULL)
- error (1, 0, "unable to reopen `%s'", rcs->path);
- if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0)
- error (1, 0, "cannot fseek RCS file");
- }
+ rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf);
gothead = 0;
- getrcsrev (fp, &key);
- while (getrcskey (fp, &key, &value, &len) >= 0)
+ if (! rcsbuf_getrevnum (&rcsbuf, &key))
+ error (1, 0, "unexpected EOF reading %s", rcs->path);
+ while (rcsbuf_getkey (&rcsbuf, &key, &value))
{
- if (strcmp (key, "log") == 0)
- {
- log = xmalloc (len);
- memcpy (log, value, len);
- loglen = len;
- }
- if (strcmp (key, "text") == 0)
+ if (STREQ (key, "log"))
+ log = rcsbuf_valcopy (&rcsbuf, value, 0, &loglen);
+ else if (STREQ (key, "text"))
{
gothead = 1;
break;
@@ -3012,20 +3612,23 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
return 1;
}
+ rcsbuf_valpolish (&rcsbuf, value, 0, &len);
+
if (fstat (fileno (fp), &sb) < 0)
error (1, errno, "cannot fstat %s", rcs->path);
- if (fclose (fp) < 0)
- error (0, errno, "cannot close %s", rcs->path);
+ rcsbuf_cache (rcs, &rcsbuf);
}
else
{
+ struct rcsbuffer *rcsbufp;
+
/* It isn't the head revision of the trunk. We'll need to
walk through the deltas. */
fp = NULL;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, &fp);
+ RCS_reparsercsfile (rcs, &fp, &rcsbuf);
if (fp == NULL)
{
@@ -3033,14 +3636,17 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
here too. Probably should change it thusly.... */
if (stat (rcs->path, &sb) < 0)
error (1, errno, "cannot stat %s", rcs->path);
+ rcsbufp = NULL;
}
else
{
if (fstat (fileno (fp), &sb) < 0)
error (1, errno, "cannot fstat %s", rcs->path);
+ rcsbufp = &rcsbuf;
}
- RCS_deltas (rcs, fp, rev, RCS_FETCH, &value, &len, &log, &loglen);
+ RCS_deltas (rcs, fp, rcsbufp, rev, RCS_FETCH, &value, &len,
+ &log, &loglen);
free_value = 1;
}
@@ -3063,7 +3669,7 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
ouroptions = rcs->expand;
for (cpp = kflags; *cpp != NULL; cpp++)
- if (strcmp (*cpp, ouroptions) == 0)
+ if (STREQ (*cpp, ouroptions))
break;
if (*cpp != NULL)
@@ -3077,17 +3683,186 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
}
}
- if (expand != KFLAG_O && expand != KFLAG_B)
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Handle special files and permissions, if that is desired. */
+ if (preserve_perms)
{
- Node *p;
- char *newvalue;
+ RCSVers *vers;
+ Node *info;
+ struct hardlink_info *hlinfo;
- p = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
- if (p == NULL)
+ vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
+ if (vp == NULL)
error (1, 0, "internal error: no revision information for %s",
rev == NULL ? rcs->head : rev);
+ vers = (RCSVers *) vp->data;
+
+ /* First we look for symlinks, which are simplest to handle. */
+ info = findnode (vers->other_delta, "symlink");
+ if (info != NULL)
+ {
+ char *dest;
+
+ if (pfn != NULL || (workfile == NULL && sout == RUN_TTY))
+ error (1, 0, "symbolic link %s:%s cannot be piped",
+ rcs->path, vers->version);
+ if (workfile == NULL)
+ dest = sout;
+ else
+ dest = workfile;
+
+ /* Remove `dest', just in case. It's okay to get ENOENT here,
+ since we just want the file not to be there. (TODO: decide
+ whether it should be considered an error for `dest' to exist
+ at this point. If so, the unlink call should be removed and
+ `symlink' should signal the error. -twp) */
+ if (unlink (dest) < 0 && existence_error (errno))
+ error (1, errno, "cannot remove %s", dest);
+ if (symlink (info->data, dest) < 0)
+ error (1, errno, "cannot create symbolic link from %s to %s",
+ dest, info->data);
+ if (free_value)
+ free (value);
+ if (free_rev)
+ free (rev);
+ return 0;
+ }
+
+ /* Next, we look at this file's hardlinks field, and see whether
+ it is linked to any other file that has been checked out.
+ If so, we don't do anything else -- just link it to that file.
- expand_keywords (rcs, (RCSVers *) p->data, nametag, log, loglen,
+ If we are checking out a file to a pipe or temporary storage,
+ none of this should matter. Hence the `workfile != NULL'
+ wrapper around the whole thing. -twp */
+
+ if (workfile != NULL)
+ {
+ info = findnode (vers->other_delta, "hardlinks");
+ if (info != NULL)
+ {
+ char *links = xstrdup (info->data);
+ char *working_dir = xgetwd();
+ char *p, *file = NULL;
+ Node *n, *uptodate_link;
+
+ /* For each file in the hardlinks field, check to see
+ if it exists, and if so, if it has been checked out
+ this iteration. */
+ uptodate_link = NULL;
+ for (p = strtok (links, " ");
+ p != NULL && uptodate_link == NULL;
+ p = strtok (NULL, " "))
+ {
+ file = (char *)
+ xmalloc (sizeof(char) *
+ (strlen(working_dir) + strlen(p) + 2));
+ sprintf (file, "%s/%s", working_dir, p);
+ n = lookup_file_by_inode (file);
+ if (n == NULL)
+ {
+ if (strcmp (p, workfile) != 0)
+ {
+ /* One of the files that WORKFILE should be
+ linked to is not even in the working directory.
+ The user should probably be warned. */
+ error (0, 0,
+ "warning: %s should be hardlinked to %s, but is missing",
+ p, workfile);
+ }
+ free (file);
+ continue;
+ }
+
+ /* hlinfo may be NULL if, for instance, a file is being
+ removed. */
+ hlinfo = (struct hardlink_info *) n->data;
+ if (hlinfo && hlinfo->checked_out)
+ uptodate_link = n;
+ free (file);
+ }
+ free (links);
+ free (working_dir);
+
+ /* If we've found a file that `workfile' is supposed to be
+ linked to, and it has been checked out since CVS was
+ invoked, then simply link workfile to that file.
+
+ If one of these conditions is not met, then we're
+ checking out workfile to a temp file or stdout, or
+ workfile is the first one in its hardlink group to be
+ checked out. Either way we must continue with a full
+ checkout. */
+
+ if (uptodate_link != NULL)
+ {
+ if (link (uptodate_link->key, workfile) < 0)
+ error (1, errno, "cannot link %s to %s",
+ workfile, uptodate_link->key);
+ hlinfo->checked_out = 1; /* probably unnecessary */
+ if (free_value)
+ free (value);
+ if (free_rev)
+ free (rev);
+ return 0;
+ }
+ }
+ }
+
+ info = findnode (vers->other_delta, "owner");
+ if (info != NULL)
+ {
+ change_rcs_owner = 1;
+ rcs_owner = (uid_t) strtoul (info->data, NULL, 10);
+ }
+ info = findnode (vers->other_delta, "group");
+ if (info != NULL)
+ {
+ change_rcs_group = 1;
+ rcs_group = (gid_t) strtoul (info->data, NULL, 10);
+ }
+ info = findnode (vers->other_delta, "permissions");
+ if (info != NULL)
+ {
+ change_rcs_mode = 1;
+ rcs_mode = (mode_t) strtoul (info->data, NULL, 8);
+ }
+ info = findnode (vers->other_delta, "special");
+ if (info != NULL)
+ {
+ /* If the size of `devtype' changes, fix the sscanf call also */
+ char devtype[16];
+
+ if (sscanf (info->data, "%16s %lu",
+ devtype, &devnum_long) < 2)
+ error (1, 0, "%s:%s has bad `special' newphrase %s",
+ workfile, vers->version, info->data);
+ devnum = devnum_long;
+ if (strcmp (devtype, "character") == 0)
+ special_file = S_IFCHR;
+ else if (strcmp (devtype, "block") == 0)
+ special_file = S_IFBLK;
+ else
+ error (0, 0, "%s is a special file of unsupported type `%s'",
+ workfile, info->data);
+ }
+ }
+#endif
+
+ if (expand != KFLAG_O && expand != KFLAG_B)
+ {
+ char *newvalue;
+
+ /* Don't fetch the delta node again if we already have it. */
+ if (vp == NULL)
+ {
+ vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
+ if (vp == NULL)
+ error (1, 0, "internal error: no revision information for %s",
+ rev == NULL ? rcs->head : rev);
+ }
+
+ expand_keywords (rcs, (RCSVers *) vp->data, nametag, log, loglen,
expand, value, len, &newvalue, &len);
if (newvalue != value)
@@ -3107,19 +3882,55 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
if (pfn != NULL)
{
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (special_file)
+ error (1, 0, "special file %s cannot be piped to anything",
+ rcs->path);
+#endif
/* The PFN interface is very simple to implement right now, as
we always have the entire file in memory. */
if (len != 0)
pfn (callerdat, value, len);
}
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ else if (special_file)
+ {
+ char *dest;
+
+ /* Can send either to WORKFILE or to SOUT, as long as SOUT is
+ not RUN_TTY. */
+ dest = workfile;
+ if (dest == NULL)
+ {
+ if (sout == RUN_TTY)
+ error (1, 0, "special file %s cannot be written to stdout",
+ rcs->path);
+ dest = sout;
+ }
+
+ /* Unlink `dest', just in case. It's okay if this provokes a
+ ENOENT error. */
+ if (unlink (dest) < 0 && existence_error (errno))
+ error (1, errno, "cannot remove %s", dest);
+ if (mknod (dest, special_file, devnum) < 0)
+ error (1, errno, "could not create special file %s",
+ dest);
+ }
+#endif
else
{
+ /* Not a special file: write to WORKFILE or SOUT. */
if (workfile == NULL)
{
if (sout == RUN_TTY)
ofp = stdout;
else
{
+ /* Symbolic links should be removed before replacement, so that
+ `fopen' doesn't follow the link and open the wrong file. */
+ if (islink (sout))
+ if (unlink_file (sout) < 0)
+ error (1, errno, "cannot remove %s", sout);
ofp = CVS_FOPEN (sout, expand == KFLAG_B ? "wb" : "w");
if (ofp == NULL)
error (1, errno, "cannot open %s", sout);
@@ -3127,6 +3938,11 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
}
else
{
+ /* Output is supposed to go to WORKFILE, so we should open that
+ file. Symbolic links should be removed first (see above). */
+ if (islink (workfile))
+ if (unlink_file (workfile) < 0)
+ error (1, errno, "cannot remove %s", workfile);
ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
if (ofp == NULL)
error (1, errno, "cannot open %s", workfile);
@@ -3171,22 +3987,56 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
nstep = nleft;
}
}
+ }
- if (workfile != NULL)
+ if (workfile != NULL)
+ {
+ int ret;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (!special_file && fclose (ofp) < 0)
+ error (1, errno, "cannot close %s", workfile);
+
+ if (change_rcs_owner || change_rcs_group)
{
- if (fclose (ofp) < 0)
- error (1, errno, "cannot close %s", workfile);
- if (chmod (workfile,
- sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)) < 0)
- error (0, errno, "cannot change mode of file %s",
+ if (chown (workfile, rcs_owner, rcs_group) < 0)
+ error (0, errno, "could not change file ownership on %s",
workfile);
}
- else if (sout != RUN_TTY)
+
+ ret = chmod (workfile,
+ change_rcs_mode
+ ? rcs_mode
+ : sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
+#else
+ if (fclose (ofp) < 0)
+ error (1, errno, "cannot close %s", workfile);
+
+ ret = chmod (workfile,
+ sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
+#endif
+ if (ret < 0)
{
- if (fclose (ofp) < 0)
- error (1, errno, "cannot close %s", sout);
+ error (0, errno, "cannot change mode of file %s",
+ workfile);
}
}
+ else if (sout != RUN_TTY)
+ {
+ if (
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ !special_file &&
+#endif
+ fclose (ofp) < 0)
+ error (1, errno, "cannot close %s", sout);
+ }
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If we are in the business of preserving hardlinks, then
+ mark this file as having been checked out. */
+ if (preserve_perms && workfile != NULL)
+ update_hardlink_info (workfile);
+#endif
if (free_value)
free (value);
@@ -3235,7 +4085,7 @@ RCS_findlock_or_tip (rcs)
lock = NULL;
for (p = locklist->list->next; p != locklist->list; p = p->next)
{
- if (strcmp (p->data, user) == 0)
+ if (STREQ (p->data, user))
{
if (lock != NULL)
{
@@ -3341,8 +4191,11 @@ compare_truncated_revnums (r, s)
FIXME: isn't the max rev always the last one?
If so, we don't even need a loop. */
+static char *max_rev PROTO ((const RCSVers *));
+
static char *
-max_rev (const RCSVers *branchnode)
+max_rev (branchnode)
+ const RCSVers *branchnode;
{
Node *head;
Node *bp;
@@ -3477,22 +4330,27 @@ RCS_addbranch (rcs, branch)
return newrevnum;
}
-/* Check in to RCSFILE with revision REV (which must be greater than the
- largest revision) and message MESSAGE (which is checked for legality).
- If FLAGS & RCS_FLAGS_DEAD, check in a dead revision. If FLAGS &
- RCS_FLAGS_QUIET, tell ci to be quiet. If FLAGS & RCS_FLAGS_MODTIME,
- use the working file's modification time for the checkin time.
- WORKFILE is the working file to check in from, or NULL to use the usual
- RCS rules for deriving it from the RCSFILE.
+/* Check in to RCSFILE with revision REV (which must be greater than
+ the largest revision) and message MESSAGE (which is checked for
+ legality). If FLAGS & RCS_FLAGS_DEAD, check in a dead revision.
+ If FLAGS & RCS_FLAGS_QUIET, tell ci to be quiet. If FLAGS &
+ RCS_FLAGS_MODTIME, use the working file's modification time for the
+ checkin time. WORKFILE is the working file to check in from, or
+ NULL to use the usual RCS rules for deriving it from the RCSFILE.
+ If FLAGS & RCS_FLAGS_KEEPFILE, don't unlink the working file;
+ unlinking the working file is standard RCS behavior, but is rarely
+ appropriate for CVS.
+
+ This function should almost exactly mimic the behavior of `rcs ci'. The
+ principal point of difference is the support here for preserving file
+ ownership and permissions in the delta nodes. This is not a clean
+ solution -- precisely because it diverges from RCS's behavior -- but
+ it doesn't seem feasible to do this anywhere else in the code. [-twp]
Return value is -1 for error (and errno is set to indicate the
error), positive for error (and an error message has been printed),
or zero for success. */
-/* TODO: RCS_checkin always unlinks the working file after checkin --
- then RCS_checkout checks it out again. The logic should probably
- be reversed here. */
-
int
RCS_checkin (rcs, workfile, message, rev, flags)
RCSNode *rcs;
@@ -3515,7 +4373,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
commitpt = NULL;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* Get basename of working file. Is there a library function to
do this? I couldn't find one. -twp */
@@ -3569,6 +4427,92 @@ RCS_checkin (rcs, workfile, message, rev, flags)
else
delta->state = xstrdup ("Exp");
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If permissions should be preserved on this project, then
+ save the permission info. */
+ if (preserve_perms)
+ {
+ Node *np;
+ struct stat sb;
+ char buf[64]; /* static buffer should be safe: see usage. -twp */
+ char *fullpath;
+
+ delta->other_delta = getlist();
+
+ if (CVS_LSTAT (workfile, &sb) < 0)
+ error (1, 1, "cannot lstat %s", workfile);
+
+ if (S_ISLNK (sb.st_mode))
+ {
+ np = getnode();
+ np->key = xstrdup ("symlink");
+ np->data = xreadlink (workfile);
+ addnode (delta->other_delta, np);
+ }
+ else
+ {
+ (void) sprintf (buf, "%u", sb.st_uid);
+ np = getnode();
+ np->key = xstrdup ("owner");
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+
+ (void) sprintf (buf, "%u", sb.st_gid);
+ np = getnode();
+ np->key = xstrdup ("group");
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+
+ (void) sprintf (buf, "%o", sb.st_mode & 07777);
+ np = getnode();
+ np->key = xstrdup ("permissions");
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+
+ /* Save device number. */
+ switch (sb.st_mode & S_IFMT)
+ {
+ case S_IFREG: break;
+ case S_IFCHR:
+ case S_IFBLK:
+ np = getnode();
+ np->key = xstrdup ("special");
+ sprintf (buf, "%s %lu",
+ ((sb.st_mode & S_IFMT) == S_IFCHR
+ ? "character" : "block"),
+ (unsigned long) sb.st_rdev);
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+ break;
+
+ default:
+ error (0, 0, "special file %s has unknown type", workfile);
+ }
+
+ /* Save hardlinks. */
+ fullpath = xgetwd();
+ fullpath = xrealloc (fullpath,
+ strlen(fullpath) + strlen(workfile) + 2);
+ sprintf (fullpath + strlen(fullpath), "/%s", workfile);
+
+ np = lookup_file_by_inode (fullpath);
+ if (np == NULL)
+ {
+ error (1, 0, "lost information on %s's linkage", workfile);
+ }
+ else
+ {
+ struct hardlink_info *hlinfo;
+ hlinfo = (struct hardlink_info *) np->data;
+ np = getnode();
+ np->key = xstrdup ("hardlinks");
+ np->data = xstrdup (hlinfo->links);
+ (void) addnode (delta->other_delta, np);
+ }
+ }
+ }
+#endif
+
/* Create a new deltatext node. */
dtext = (Deltatext *) xmalloc (sizeof (Deltatext));
memset (dtext, 0, sizeof (Deltatext));
@@ -3607,7 +4551,9 @@ RCS_checkin (rcs, workfile, message, rev, flags)
dtext->version = xstrdup (newrev);
bufsize = 0;
- get_file(workfile, workfile, "r", &dtext->text, &bufsize, &dtext->len);
+ get_file (workfile, workfile,
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+ &dtext->text, &bufsize, &dtext->len);
if (!checkin_quiet)
{
@@ -3616,6 +4562,9 @@ RCS_checkin (rcs, workfile, message, rev, flags)
cvs_output ("\n", 1);
}
+ /* We are probably about to invalidate any cached file. */
+ rcsbuf_cache_close ();
+
fout = rcs_internal_lockfile (rcs->path);
RCS_putadmin (rcs, fout);
RCS_putdtree (rcs, rcs->head, fout);
@@ -3627,12 +4576,12 @@ RCS_checkin (rcs, workfile, message, rev, flags)
rcs_internal_unlockfile (fout, rcs->path);
freedeltatext (dtext);
- /* Removing the file here is an RCS user-visible behavior which
- we almost surely do not need in the CVS case. In fact, getting
- rid of it should clean up link_file and friends in import.c. */
- if (unlink_file (workfile) < 0)
- /* FIXME-update-dir: message does not include update_dir. */
- error (0, errno, "cannot remove %s", workfile);
+ if ((flags & RCS_FLAGS_KEEPFILE) == 0)
+ {
+ if (unlink_file (workfile) < 0)
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (0, errno, "cannot remove %s", workfile);
+ }
if (!checkin_quiet)
cvs_output ("done\n", 5);
@@ -3672,7 +4621,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
goto checkin_done;
}
else if (commitpt->next == NULL
- || strcmp (commitpt->version, rcs->head) == 0)
+ || STREQ (commitpt->version, rcs->head))
delta->version = increment_revnum (commitpt->version);
else
delta->version = RCS_addbranch (rcs, commitpt->version);
@@ -3776,7 +4725,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
nodep = findnode (RCS_getlocks (rcs), commitpt->version);
if (nodep != NULL)
{
- if (strcmp (nodep->data, delta->author) != 0)
+ if (! STREQ (nodep->data, delta->author))
{
error (0, 0, "%s: revision %s locked by %s",
rcs->path,
@@ -3798,13 +4747,13 @@ RCS_checkin (rcs, workfile, message, rev, flags)
tmpfile = cvs_temp_name();
status = RCS_checkout (rcs, NULL, commitpt->version, NULL,
((rcs->expand != NULL
- && strcmp (rcs->expand, "b") == 0)
+ && STREQ (rcs->expand, "b"))
? "-kb"
: "-ko"),
tmpfile,
(RCSCHECKOUTPROC)0, NULL);
if (status != 0)
- error (1, status < 0 ? errno : 0,
+ error (1, 0,
"could not check out revision %s of `%s'",
commitpt->version, rcs->path);
@@ -3815,18 +4764,18 @@ RCS_checkin (rcs, workfile, message, rev, flags)
/* Diff options should include --binary if the RCS file has -kb set
in its `expand' field. */
- diffopts = (rcs->expand != NULL && strcmp (rcs->expand, "b") == 0
+ diffopts = (rcs->expand != NULL && STREQ (rcs->expand, "b")
? "-a -n --binary"
: "-a -n");
- if (strcmp (commitpt->version, rcs->head) == 0 &&
+ if (STREQ (commitpt->version, rcs->head) &&
numdots (delta->version) == 1)
{
/* If this revision is being inserted on the trunk, the change text
for the new delta should be the contents of the working file ... */
bufsize = 0;
get_file (workfile, workfile,
- rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r",
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&dtext->text, &bufsize, &dtext->len);
/* ... and the change text for the old delta should be a diff. */
@@ -3862,7 +4811,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
This should cause no harm, but doesn't strike me as
immensely clean. */
get_file (changefile, changefile,
- rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r",
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&commitpt->text->text, &bufsize, &commitpt->text->len);
/* If COMMITPT->TEXT->TEXT is NULL, it means that CHANGEFILE
@@ -3899,7 +4848,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
/* See the comment above, at the other get_file invocation,
regarding binary vs. text. */
get_file (changefile, changefile,
- rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r",
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&dtext->text, &bufsize,
&dtext->len);
if (dtext->text == NULL)
@@ -3923,7 +4872,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
if (numdots (commitpt->version) == numdots (delta->version))
{
- if (strcmp (commitpt->version, rcs->head) == 0)
+ if (STREQ (commitpt->version, rcs->head))
{
delta->next = rcs->head;
rcs->head = xstrdup (delta->version);
@@ -3953,12 +4902,12 @@ RCS_checkin (rcs, workfile, message, rev, flags)
RCS_rewrite (rcs, dtext, commitpt->version);
- /* Removing the file here is an RCS user-visible behavior which
- we almost surely do not need in the CVS case. In fact, getting
- rid of it should clean up link_file and friends in import.c. */
- if (unlink_file (workfile) < 0)
- /* FIXME-update-dir: message does not include update_dir. */
- error (1, errno, "cannot remove %s", workfile);
+ if ((flags & RCS_FLAGS_KEEPFILE) == 0)
+ {
+ if (unlink_file (workfile) < 0)
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (1, errno, "cannot remove %s", workfile);
+ }
if (unlink_file (tmpfile) < 0)
error (0, errno, "cannot remove %s", tmpfile);
if (unlink_file (changefile) < 0)
@@ -4011,42 +4960,70 @@ RCS_cmp_file (rcs, rev, options, filename)
int retcode;
if (options != NULL && options[0] != '\0')
- binary = (strcmp (options, "-kb") == 0);
+ binary = STREQ (options, "-kb");
else
{
char *expand;
expand = RCS_getexpand (rcs);
- if (expand != NULL && strcmp (expand, "b") == 0)
+ if (expand != NULL && STREQ (expand, "b"))
binary = 1;
else
binary = 0;
}
- fp = CVS_FOPEN (filename, binary ? FOPEN_BINARY_READ : "r");
-
- data.filename = filename;
- data.fp = fp;
- data.different = 0;
-
- retcode = RCS_checkout (rcs, (char *) NULL, rev, (char *) NULL,
- options, RUN_TTY, cmp_file_buffer,
- (void *) &data);
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If CVS is to deal properly with special files (when
+ PreservePermissions is on), the best way is to check out the
+ revision to a temporary file and call `xcmp' on the two disk
+ files. xcmp needs to handle non-regular files properly anyway,
+ so calling it simplifies RCS_cmp_file. We *could* just yank
+ the delta node out of the version tree and look for device
+ numbers, but writing to disk and calling xcmp is a better
+ abstraction (therefore probably more robust). -twp */
- /* If we have not yet found a difference, make sure that we are at
- the end of the file. */
- if (! data.different)
+ if (preserve_perms)
{
- if (getc (fp) != EOF)
- data.different = 1;
- }
+ char *tmp;
- fclose (fp);
+ tmp = cvs_temp_name();
+ retcode = RCS_checkout(rcs, NULL, rev, NULL, options, tmp, NULL, NULL);
+ if (retcode != 0)
+ return 1;
- if (retcode != 0)
- return 1;
+ retcode = xcmp (tmp, filename);
+ if (CVS_UNLINK (tmp) < 0)
+ error (0, errno, "cannot remove %s", tmp);
+ return retcode;
+ }
+ else
+#endif
+ {
+ fp = CVS_FOPEN (filename, binary ? FOPEN_BINARY_READ : "r");
+
+ data.filename = filename;
+ data.fp = fp;
+ data.different = 0;
+
+ retcode = RCS_checkout (rcs, (char *) NULL, rev, (char *) NULL,
+ options, RUN_TTY, cmp_file_buffer,
+ (void *) &data);
+
+ /* If we have not yet found a difference, make sure that we are at
+ the end of the file. */
+ if (! data.different)
+ {
+ if (getc (fp) != EOF)
+ data.different = 1;
+ }
+
+ fclose (fp);
- return data.different;
+ if (retcode != 0)
+ return 1;
+
+ return data.different;
+ }
}
/* This is a subroutine of RCS_cmp_file. It is passed to
@@ -4114,12 +5091,12 @@ RCS_settag (rcs, tag, rev)
Node *node;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* FIXME: This check should be moved to RCS_check_tag. There is no
reason for it to be here. */
- if (strcmp (tag, TAG_BASE) == 0
- || strcmp (tag, TAG_HEAD) == 0)
+ if (STREQ (tag, TAG_BASE)
+ || STREQ (tag, TAG_HEAD))
{
/* Print the name of the tag might be considered redundant
with the caller, which also prints it. Perhaps this helps
@@ -4173,7 +5150,7 @@ RCS_deltag (rcs, tag)
List *symbols;
Node *node;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
symbols = RCS_symbols (rcs);
if (symbols == NULL)
@@ -4196,11 +5173,14 @@ RCS_setbranch (rcs, rev)
const char *rev;
{
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ if (rev && ! *rev)
+ rev = NULL;
if (rev == NULL && rcs->branch == NULL)
return 0;
- if (rev != NULL && rcs->branch != NULL && strcmp (rev, rcs->branch) == 0)
+ if (rev != NULL && rcs->branch != NULL && STREQ (rev, rcs->branch))
return 0;
if (rcs->branch != NULL)
@@ -4230,7 +5210,7 @@ RCS_lock (rcs, rev, lock_quiet)
char *xrev = NULL;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
locks = RCS_getlocks (rcs);
if (locks == NULL)
@@ -4274,7 +5254,7 @@ RCS_lock (rcs, rev, lock_quiet)
p = findnode (locks, xrev);
if (p != NULL)
{
- if (strcmp (p->data, user) == 0)
+ if (STREQ (p->data, user))
{
/* We already own the lock on this revision, so do nothing. */
free (xrev);
@@ -4326,7 +5306,7 @@ RCS_unlock (rcs, rev, unlock_quiet)
user = getcaller();
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* If rev is NULL, unlock the latest revision (first in
rcs->locks) held by the caller. */
@@ -4353,7 +5333,7 @@ RCS_unlock (rcs, rev, unlock_quiet)
lock = NULL;
for (p = locks->list->next; p != locks->list; p = p->next)
{
- if (strcmp (p->data, user) == 0)
+ if (STREQ (p->data, user))
{
if (lock != NULL)
{
@@ -4392,7 +5372,7 @@ RCS_unlock (rcs, rev, unlock_quiet)
return 0;
}
- if (strcmp (lock->data, user) != 0)
+ if (! STREQ (lock->data, user))
{
/* If the revision is locked by someone else, notify
them. Note that this shouldn't ever happen if RCS_unlock
@@ -4428,7 +5408,7 @@ RCS_addaccess (rcs, user)
char *access, *a;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->access == NULL)
rcs->access = xstrdup (user);
@@ -4437,7 +5417,7 @@ RCS_addaccess (rcs, user)
access = xstrdup (rcs->access);
for (a = strtok (access, " "); a != NULL; a = strtok (NULL, " "))
{
- if (strcmp (a, user) == 0)
+ if (STREQ (a, user))
{
free (access);
return;
@@ -4461,7 +5441,7 @@ RCS_delaccess (rcs, user)
int ulen;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->access == NULL)
return;
@@ -4492,7 +5472,7 @@ RCS_getaccess (rcs)
RCSNode *rcs;
{
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
return rcs->access;
}
@@ -4508,7 +5488,7 @@ findtag (node, arg)
{
char *rev = (char *)arg;
- if (strcmp (node->data, rev) == 0)
+ if (STREQ (node->data, rev))
return 1;
else
return 0;
@@ -4601,7 +5581,7 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
{
/* A range consisting of a branch number means the latest revision
on that branch. */
- if (RCS_isbranch (rcs, rev1) && strcmp (rev1, rev2) == 0)
+ if (RCS_isbranch (rcs, rev1) && STREQ (rev1, rev2))
rev1 = rev2 = RCS_getbranch (rcs, rev1, 0);
else
{
@@ -4692,12 +5672,12 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
*bp = '.';
}
}
- else if (strcmp (rev1, branchpoint) != 0)
+ else if (! STREQ (rev1, branchpoint))
{
/* Walk deltas from BRANCHPOINT on, looking for REV1. */
nodep = findnode (rcs->versions, branchpoint);
revp = (RCSVers *) nodep->data;
- while (revp->next != NULL && strcmp (revp->next, rev1) != 0)
+ while (revp->next != NULL && ! STREQ (revp->next, rev1))
{
revp = (RCSVers *) nodep->data;
nodep = findnode (rcs->versions, revp->next);
@@ -4746,7 +5726,7 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
revp = (RCSVers *) nodep->data;
if (rev2 != NULL)
- found = (strcmp (revp->version, rev2) == 0);
+ found = STREQ (revp->version, rev2);
next = revp->next;
if ((!found && next != NULL) || rev2_inclusive || rev2 == NULL)
@@ -4843,13 +5823,6 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
if (status > 0)
goto delrev_done;
- else if (status < 0)
- {
- error (0, errno,
- "cannot check out revision %s of %s", after, rcs->path);
- goto delrev_done;
- }
-
if (before == NULL)
{
/* We are deleting revisions from the head of the tree,
@@ -4877,12 +5850,6 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
(RCSCHECKOUTPROC)0, NULL);
if (status > 0)
goto delrev_done;
- else if (status < 0)
- {
- error (0, errno, "cannot check out revision %s of %s",
- before, rcs->path);
- goto delrev_done;
- }
outfile = cvs_temp_name();
status = diff_exec (beforefile, afterfile, "-n", outfile);
@@ -4927,7 +5894,7 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
outdated. (FIXME: would it be safe to use the `dead' field for
this? Doubtful.) */
for (next = rev1;
- next != NULL && (after == NULL || strcmp (next, after) != 0);
+ next != NULL && (after == NULL || ! STREQ (next, after));
next = revp->next)
{
nodep = findnode (rcs->versions, next);
@@ -4952,13 +5919,13 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
/* beforep's ->next field already should be equal to after,
which I think is always NULL in this case. */
;
- else if (strcmp (rev1, branchpoint) == 0)
+ else if (STREQ (rev1, branchpoint))
{
nodep = findnode (rcs->versions, before);
revp = (RCSVers *) nodep->data;
nodep = revp->branches->list->next;
while (nodep != revp->branches->list &&
- strcmp (nodep->key, rev1) != 0)
+ ! STREQ (nodep->key, rev1))
nodep = nodep->next;
assert (nodep != revp->branches->list);
if (after == NULL)
@@ -5478,9 +6445,10 @@ rcs_change_text (name, textbuf, textlen, diffbuf, difflen, retbuf, retlen)
On error, give a fatal error. */
static void
-RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
+RCS_deltas (rcs, fp, rcsbuf, version, op, text, len, log, loglen)
RCSNode *rcs;
FILE *fp;
+ struct rcsbuffer *rcsbuf;
char *version;
enum rcs_delta_op op;
char **text;
@@ -5488,6 +6456,7 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
char **log;
size_t *loglen;
{
+ struct rcsbuffer rcsbuf_local;
char *branchversion;
char *cpversion;
char *key;
@@ -5497,7 +6466,6 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
RCSVers *prev_vers;
RCSVers *trunk_vers;
char *next;
- int n;
int ishead, isnext, isversion, onbranch;
Node *node;
struct linevector headlines;
@@ -5507,11 +6475,8 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
if (fp == NULL)
{
- fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
- if (fp == NULL)
- error (1, 0, "unable to reopen `%s'", rcs->path);
- if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0)
- error (1, 0, "cannot fseek RCS file");
+ rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf_local);
+ rcsbuf = &rcsbuf_local;
}
ishead = 1;
@@ -5538,9 +6503,10 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
*cpversion = '\0';
do {
- getrcsrev (fp, &key);
+ if (! rcsbuf_getrevnum (rcsbuf, &key))
+ error (1, 0, "unexpected EOF reading RCS file %s", rcs->path);
- if (next != NULL && strcmp (next, key) != 0)
+ if (next != NULL && ! STREQ (next, key))
{
/* This is not the next version we need. It is a branch
version which we want to ignore. */
@@ -5565,27 +6531,30 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
next = vers->next;
/* Compare key and trunkversion now, because key points to
- storage controlled by getrcskey. */
- if (strcmp (branchversion, key) == 0)
+ storage controlled by rcsbuf_getkey. */
+ if (STREQ (branchversion, key))
isversion = 1;
else
isversion = 0;
}
- while ((n = getrcskey (fp, &key, &value, &vallen)) >= 0)
+ while (1)
{
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s does not appear to be a valid rcs file",
+ rcs->path);
+
if (log != NULL
&& isversion
- && strcmp (key, "log") == 0
- && strcmp (branchversion, version) == 0)
+ && STREQ (key, "log")
+ && STREQ (branchversion, version))
{
- *log = xmalloc (vallen);
- memcpy (*log, value, vallen);
- *loglen = vallen;
+ *log = rcsbuf_valcopy (rcsbuf, value, 0, loglen);
}
- if (strcmp (key, "text") == 0)
+ if (STREQ (key, "text"))
{
+ rcsbuf_valpolish (rcsbuf, value, 0, &vallen);
if (ishead)
{
if (! linevector_add (&curlines, value, vallen, NULL, 0))
@@ -5604,14 +6573,12 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
break;
}
}
- if (n < 0)
- goto l_error;
if (isversion)
{
/* This is either the version we want, or it is the
branchpoint to the version we want. */
- if (strcmp (branchversion, version) == 0)
+ if (STREQ (branchversion, version))
{
/* This is the version we want. */
linevector_copy (&headlines, &curlines);
@@ -5691,9 +6658,8 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
} while (next != NULL);
free (branchversion);
-
- if (fclose (fp) < 0)
- error (0, errno, "cannot close %s", rcs->path);
+
+ rcsbuf_cache (rcs, rcsbuf);
if (! foundhead)
error (1, 0, "could not find desired version %s in %s",
@@ -5797,125 +6763,148 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
linevector_free (&trunklines);
return;
-
- l_error:
- if (ferror (fp))
- error (1, errno, "cannot read %s", rcs->path);
- else
- error (1, 0, "%s does not appear to be a valid rcs file",
- rcs->path);
}
+/* Read the information for a single delta from the RCS buffer RCSBUF,
+ whose name is RCSFILE. *KEYP and *VALP are either NULL, or the
+ first key/value pair to read, as set by rcsbuf_getkey. Return NULL
+ if there are no more deltas. Store the key/value pair which
+ terminated the read in *KEYP and *VALP. */
+
static RCSVers *
-getdelta (fp, rcsfile)
- FILE *fp;
+getdelta (rcsbuf, rcsfile, keyp, valp)
+ struct rcsbuffer *rcsbuf;
char *rcsfile;
+ char **keyp;
+ char **valp;
{
RCSVers *vnode;
char *key, *value, *cp;
- long fpos;
Node *kv;
- vnode = (RCSVers *) xmalloc (sizeof (RCSVers));
- memset (vnode, 0, sizeof (RCSVers));
-
- /* Get revision number. This uses getrcskey because it doesn't
- croak when encountering unexpected input. As a result, we have
- to play unholy games with `key' and `value'. */
- fpos = ftell (fp);
- getrcskey (fp, &key, &value, NULL);
+ /* Get revision number if it wasn't passed in. This uses
+ rcsbuf_getkey because it doesn't croak when encountering
+ unexpected input. As a result, we have to play unholy games
+ with `key' and `value'. */
+ if (*keyp != NULL)
+ {
+ key = *keyp;
+ value = *valp;
+ }
+ else
+ {
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s: unexpected EOF", rcsfile);
+ }
/* Make sure that it is a revision number and not a cabbage
or something. */
for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
/* do nothing */ ;
- if (*cp != '\0' || strncmp (RCSDATE, value, strlen (RCSDATE)) != 0)
+ /* Note that when comparing with RCSDATE, we are not massaging
+ VALUE from the string found in the RCS file. This is OK since
+ we know exactly what to expect. */
+ if (*cp != '\0' || strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) != 0)
{
- (void) fseek (fp, fpos, SEEK_SET);
- free (vnode);
+ *keyp = key;
+ *valp = value;
return NULL;
}
+
+ vnode = (RCSVers *) xmalloc (sizeof (RCSVers));
+ memset (vnode, 0, sizeof (RCSVers));
+
vnode->version = xstrdup (key);
- /* grab the value of the date from value */
- cp = value + strlen (RCSDATE);/* skip the "date" keyword */
+ /* Grab the value of the date from value. Note that we are not
+ massaging VALUE from the string found in the RCS file. */
+ cp = value + (sizeof RCSDATE) - 1; /* skip the "date" keyword */
while (whitespace (*cp)) /* take space off front of value */
cp++;
vnode->date = xstrdup (cp);
/* Get author field. */
- (void) getrcskey (fp, &key, &value, NULL);
- /* FIXME: should be using errno in case of ferror. */
- if (key == NULL || strcmp (key, "author") != 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (! STREQ (key, "author"))
error (1, 0, "\
-unable to parse rcs file; `author' not in the expected place");
- vnode->author = xstrdup (value);
+unable to parse %s; `author' not in the expected place", rcsfile);
+ vnode->author = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
/* Get state field. */
- (void) getrcskey (fp, &key, &value, NULL);
- /* FIXME: should be using errno in case of ferror. */
- if (key == NULL || strcmp (key, "state") != 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (! STREQ (key, "state"))
error (1, 0, "\
-unable to parse rcs file; `state' not in the expected place");
- vnode->state = xstrdup (value);
- if (strcmp (value, "dead") == 0)
+unable to parse %s; `state' not in the expected place", rcsfile);
+ vnode->state = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
+ if (STREQ (value, "dead"))
{
vnode->dead = 1;
}
/* Note that "branches" and "next" are in fact mandatory, according
- to doc/RCSFILES. We perhaps should be giving an error if they
- are not there. */
+ to doc/RCSFILES. */
/* fill in the branch list (if any branches exist) */
- fpos = ftell (fp);
- (void) getrcskey (fp, &key, &value, NULL);
- /* FIXME: should be handling various error conditions better. */
- if (key != NULL && strcmp (key, RCSDESC) == 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (STREQ (key, RCSDESC))
{
- (void) fseek (fp, fpos, SEEK_SET);
+ *keyp = key;
+ *valp = value;
+ /* Probably could/should be a fatal error. */
+ error (0, 0, "warning: 'branches' keyword missing from %s", rcsfile);
return vnode;
}
if (value != (char *) NULL)
{
vnode->branches = getlist ();
+ /* Note that we are not massaging VALUE from the string found
+ in the RCS file. */
do_branches (vnode->branches, value);
}
/* fill in the next field if there is a next revision */
- fpos = ftell (fp);
- (void) getrcskey (fp, &key, &value, NULL);
- /* FIXME: should be handling various error conditions better. */
- if (key != NULL && strcmp (key, RCSDESC) == 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
{
- (void) fseek (fp, fpos, SEEK_SET);
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (STREQ (key, RCSDESC))
+ {
+ *keyp = key;
+ *valp = value;
+ /* Probably could/should be a fatal error. */
+ error (0, 0, "warning: 'next' keyword missing from %s", rcsfile);
return vnode;
}
if (value != (char *) NULL)
- vnode->next = xstrdup (value);
+ vnode->next = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
/*
* XXX - this is where we put the symbolic link stuff???
* (into newphrases in the deltas).
*/
- /* FIXME: Does not correctly handle errors, e.g. from stdio. */
while (1)
{
- fpos = ftell (fp);
- if (getrcskey (fp, &key, &value, NULL) < 0)
- break;
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
- assert (key != NULL);
-
- if (strcmp (key, RCSDESC) == 0)
+ if (STREQ (key, RCSDESC))
break;
/* Enable use of repositories created by certain obsolete
versions of CVS. This code should remain indefinately;
there is no procedure for converting old repositories, and
checking for it is harmless. */
- if (strcmp(key, RCSDEAD) == 0)
+ if (STREQ (key, RCSDEAD))
{
vnode->dead = 1;
if (vnode->state != NULL)
@@ -5926,6 +6915,9 @@ unable to parse rcs file; `state' not in the expected place");
/* if we have a new revision number, we're done with this delta */
for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
/* do nothing */ ;
+ /* Note that when comparing with RCSDATE, we are not massaging
+ VALUE from the string found in the RCS file. This is OK
+ since we know exactly what to expect. */
if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
break;
@@ -5936,7 +6928,7 @@ unable to parse rcs file; `state' not in the expected place");
kv = getnode ();
kv->type = RCSFIELD;
kv->key = xstrdup (key);
- kv->data = xstrdup (value);
+ kv->data = rcsbuf_valcopy (rcsbuf, value, 1, (size_t *) NULL);
if (addnode (vnode->other_delta, kv) != 0)
{
/* Complaining about duplicate keys in newphrases seems
@@ -5948,11 +6940,11 @@ unable to parse rcs file; `state' not in the expected place");
key, rcsfile);
freenode (kv);
}
- }
+ }
- /* We got here because we read beyond the end of a delta. Seek back
- to the beginning of the erroneous read. */
- (void) fseek (fp, fpos, SEEK_SET);
+ /* Return the key which caused us to fail back to the caller. */
+ *keyp = key;
+ *valp = value;
return vnode;
}
@@ -5973,25 +6965,21 @@ freedeltatext (d)
}
static Deltatext *
-RCS_getdeltatext (rcs, fp)
+RCS_getdeltatext (rcs, fp, rcsbuf)
RCSNode *rcs;
FILE *fp;
+ struct rcsbuffer *rcsbuf;
{
char *num;
char *key, *value;
- int n;
Node *p;
Deltatext *d;
- size_t textlen;
/* Get the revision number. */
- n = getrevnum (fp, &num);
- if (ferror (fp))
- error (1, errno, "%s: cannot read", rcs->path);
- if (n == EOF)
+ if (! rcsbuf_getrevnum (rcsbuf, &num))
{
- /* If n == EOF and num == NULL, it means we reached EOF
- naturally. That's fine. */
+ /* If num == NULL, it means we reached EOF naturally. That's
+ fine. */
if (num == NULL)
return NULL;
else
@@ -6007,36 +6995,36 @@ RCS_getdeltatext (rcs, fp)
d->version = xstrdup (num);
/* Get the log message. */
- if (getrcskey (fp, &key, &value, NULL) < 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num);
- if (strcmp (key, "log") != 0)
+ if (! STREQ (key, "log"))
error (1, 0, "%s, delta %s: expected `log', got `%s'",
rcs->path, num, key);
- d->log = xstrdup (value);
+ d->log = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
/* Get random newphrases. */
d->other = getlist();
- for (n = getrcskey (fp, &key, &value, &textlen);
- n >= 0 && strcmp (key, "text") != 0;
- n = getrcskey (fp, &key, &value, &textlen))
+ while (1)
{
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num);
+
+ if (STREQ (key, "text"))
+ break;
+
p = getnode();
p->type = RCSFIELD;
p->key = xstrdup (key);
- p->data = xstrdup (value);
+ p->data = rcsbuf_valcopy (rcsbuf, value, 1, (size_t *) NULL);
if (addnode (d->other, p) < 0)
{
error (0, 0, "warning: %s, delta %s: duplicate field `%s'",
rcs->path, num, key);
}
}
- if (n < 0)
- error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num);
/* Get the change text. We already know that this key is `text'. */
- d->text = (char *) malloc (textlen + 1);
- d->len = textlen;
- memcpy (d->text, value, textlen);
+ d->text = rcsbuf_valcopy (rcsbuf, value, 0, &d->len);
return d;
}
@@ -6053,11 +7041,23 @@ RCS_getdeltatext (rcs, fp)
not get corrupted. */
static int
-putsymbol_proc (symnode, fp)
+putsymbol_proc (symnode, fparg)
Node *symnode;
- void *fp;
+ void *fparg;
{
- return fprintf ((FILE *) fp, "\n\t%s:%s", symnode->key, symnode->data);
+ FILE *fp = (FILE *) fparg;
+
+ /* A fiddly optimization: this code used to just call fprintf, but
+ in an old repository with hundreds of tags this can get called
+ hundreds of thousands of times when doing a cvs tag. Since
+ tagging is a relatively common operation, and using putc and
+ fputs is just as comprehensible, the change is worthwhile. */
+ putc ('\n', fp);
+ putc ('\t', fp);
+ fputs (symnode->key, fp);
+ putc (':', fp);
+ fputs (symnode->data, fp);
+ return 0;
}
static int putlock_proc PROTO ((Node *, void *));
@@ -6109,9 +7109,9 @@ putrcsfield_proc (node, vfp)
/* desc, log and text fields should not be terminated with semicolon;
all other fields should be. */
- if (strcmp (node->key, "desc") != 0 &&
- strcmp (node->key, "log") != 0 &&
- strcmp (node->key, "text") != 0)
+ if (! STREQ (node->key, "desc") &&
+ ! STREQ (node->key, "log") &&
+ ! STREQ (node->key, "text"))
{
putc (';', fp);
}
@@ -6141,7 +7141,15 @@ RCS_putadmin (rcs, fp)
fputs (";\n", fp);
fputs (RCSSYMBOLS, fp);
- walklist (RCS_symbols(rcs), putsymbol_proc, (void *) fp);
+ /* If we haven't had to convert the symbols to a list yet, don't
+ force a conversion now; just write out the string. */
+ if (rcs->symbols == NULL && rcs->symbols_data != NULL)
+ {
+ fputs ("\n\t", fp);
+ fputs (rcs->symbols_data, fp);
+ }
+ else
+ walklist (RCS_symbols (rcs), putsymbol_proc, (void *) fp);
fputs (";\n", fp);
fputs ("locks", fp);
@@ -6159,7 +7167,7 @@ RCS_putadmin (rcs, fp)
expand_at_signs (rcs->comment, (off_t) strlen (rcs->comment), fp);
fputs ("@;\n", fp);
}
- if (rcs->expand && strcmp (rcs->expand, "kv") != 0)
+ if (rcs->expand && ! STREQ (rcs->expand, "kv"))
fprintf (fp, "%s\t@%s@;\n", RCSEXPAND, rcs->expand);
walklist (rcs->other, putrcsfield_proc, (void *) fp);
@@ -6281,40 +7289,68 @@ putdeltatext (fp, d)
increasing order.) */
static void
-RCS_copydeltas (rcs, fin, fout, newdtext, insertpt)
+RCS_copydeltas (rcs, fin, rcsbufin, fout, newdtext, insertpt)
RCSNode *rcs;
FILE *fin;
+ struct rcsbuffer *rcsbufin;
FILE *fout;
Deltatext *newdtext;
char *insertpt;
{
- Deltatext *dtext;
+ int actions;
RCSVers *dadmin;
Node *np;
int insertbefore, found;
+ char *bufrest;
+ int nls;
+ size_t buflen;
+ char buf[8192];
+ int got;
+
+ /* Count the number of versions for which we have to do some
+ special operation. */
+ actions = walklist (rcs->versions, count_delta_actions, (void *) NULL);
/* Make a note of whether NEWDTEXT should be inserted
before or after its INSERTPT. */
insertbefore = (newdtext != NULL && numdots (newdtext->version) == 1);
- found = 0;
- while ((dtext = RCS_getdeltatext (rcs, fin)) != NULL)
+ while (actions != 0 || newdtext != NULL)
{
- found = (insertpt != NULL && strcmp (dtext->version, insertpt) == 0);
+ Deltatext *dtext;
+
+ dtext = RCS_getdeltatext (rcs, fin, rcsbufin);
+
+ /* We shouldn't hit EOF here, because that would imply that
+ some action was not taken, or that we could not insert
+ NEWDTEXT. */
+ if (dtext == NULL)
+ error (1, 0, "internal error: EOF too early in RCS_copydeltas");
+
+ found = (insertpt != NULL && STREQ (dtext->version, insertpt));
if (found && insertbefore)
+ {
putdeltatext (fout, newdtext);
+ newdtext = NULL;
+ insertpt = NULL;
+ }
np = findnode (rcs->versions, dtext->version);
dadmin = (RCSVers *) np->data;
/* If this revision has been outdated, just skip it. */
if (dadmin->outdated)
+ {
+ --actions;
continue;
+ }
/* Update the change text for this delta. New change text
data may come from cvs admin -m, cvs admin -o, or cvs ci. */
if (dadmin->text != NULL)
{
+ if (dadmin->text->log != NULL || dadmin->text->text != NULL)
+ --actions;
if (dadmin->text->log != NULL)
{
free (dtext->log);
@@ -6333,9 +7369,92 @@ RCS_copydeltas (rcs, fin, fout, newdtext, insertpt)
freedeltatext (dtext);
if (found && !insertbefore)
+ {
putdeltatext (fout, newdtext);
+ newdtext = NULL;
+ insertpt = NULL;
+ }
+ }
+
+ /* Copy the rest of the file directly, without bothering to
+ interpret it. The caller will handle error checking by calling
+ ferror.
+
+ We just wrote a newline to the file, either in putdeltatext or
+ in the caller. However, we may not have read the corresponding
+ newline from the file, because rcsbuf_getkey returns as soon as
+ it finds the end of the '@' string for the desc or text key.
+ Therefore, we may read three newlines when we should really
+ only write two, and we check for that case here. This is not
+ an semantically important issue; we only do it to make our RCS
+ files look traditional. */
+
+ nls = 3;
+
+ rcsbuf_get_buffered (rcsbufin, &bufrest, &buflen);
+ if (buflen > 0)
+ {
+ if (bufrest[0] != '\n'
+ || strncmp (bufrest, "\n\n\n", buflen < 3 ? buflen : 3) != 0)
+ {
+ nls = 0;
+ }
+ else
+ {
+ if (buflen < 3)
+ nls -= buflen;
+ else
+ {
+ ++bufrest;
+ --buflen;
+ nls = 0;
+ }
+ }
+
+ fwrite (bufrest, 1, buflen, fout);
+ }
+
+ while ((got = fread (buf, 1, sizeof buf, fin)) != 0)
+ {
+ if (nls > 0
+ && got >= nls
+ && buf[0] == '\n'
+ && strncmp (buf, "\n\n\n", nls) == 0)
+ {
+ fwrite (buf + 1, 1, got - 1, fout);
+ }
+ else
+ {
+ fwrite (buf, 1, got, fout);
+ }
+
+ nls = 0;
+ }
+}
+
+/* A helper procedure for RCS_copydeltas. This is called via walklist
+ to count the number of RCS revisions for which some special action
+ is required. */
+
+int
+count_delta_actions (np, ignore)
+ Node *np;
+ void *ignore;
+{
+ RCSVers *dadmin;
+
+ dadmin = (RCSVers *) np->data;
+
+ if (dadmin->outdated)
+ return 1;
+
+ if (dadmin->text != NULL
+ && (dadmin->text->log != NULL || dadmin->text->text != NULL))
+ {
+ return 1;
}
- putc ('\n', fout);
+
+ return 0;
}
/* RCS_internal_lockfile and RCS_internal_unlockfile perform RCS-style
@@ -6443,6 +7562,12 @@ rcs_internal_unlockfile (fp, rcsfile)
corrupting the repository. */
if (ferror (fp))
+ /* The only case in which using errno here would be meaningful
+ is if we happen to have left errno unmolested since the call
+ which produced the error (e.g. fprintf). That is pretty
+ fragile even if it happens to sometimes be true. The real
+ solution is to check each call to fprintf rather than waiting
+ until the end like this. */
error (1, 0, "error writing to lock file %s", lockfile);
if (fclose (fp) == EOF)
error (1, errno, "error closing lock file %s", lockfile);
@@ -6486,6 +7611,7 @@ RCS_rewrite (rcs, newdtext, insertpt)
char *insertpt;
{
FILE *fin, *fout;
+ struct rcsbuffer rcsbufin;
if (noexec)
return;
@@ -6497,10 +7623,7 @@ RCS_rewrite (rcs, newdtext, insertpt)
RCS_putdesc (rcs, fout);
/* Open the original RCS file and seek to the first delta text. */
- if ((fin = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ)) == NULL)
- error (1, errno, "cannot open RCS file `%s' for reading", rcs->path);
- if (fseek (fin, rcs->delta_pos, SEEK_SET) < 0)
- error (1, errno, "cannot fseek in RCS file %s", rcs->path);
+ rcsbuf_cache_open (rcs, rcs->delta_pos, &fin, &rcsbufin);
/* Update delta_pos to the current position in the output file.
Do NOT move these statements: they must be done after fin has
@@ -6510,10 +7633,19 @@ RCS_rewrite (rcs, newdtext, insertpt)
if (rcs->delta_pos == -1)
error (1, errno, "cannot ftell in RCS file %s", rcs->path);
- RCS_copydeltas (rcs, fin, fout, newdtext, insertpt);
+ RCS_copydeltas (rcs, fin, &rcsbufin, fout, newdtext, insertpt);
+ /* We don't want to call rcsbuf_cache here, since we're about to
+ delete the file. */
+ rcsbuf_close (&rcsbufin);
if (ferror (fin))
- error (0, errno, "warning: when closing RCS file `%s'", rcs->path);
+ /* The only case in which using errno here would be meaningful
+ is if we happen to have left errno unmolested since the call
+ which produced the error (e.g. fread). That is pretty
+ fragile even if it happens to sometimes be true. The real
+ solution is to make sure that all the code which reads
+ from fin checks for errors itself (some does, some doesn't). */
+ error (0, 0, "warning: when closing RCS file `%s'", rcs->path);
if (fclose (fin) < 0)
error (0, errno, "warning: closing RCS file `%s'", rcs->path);
@@ -6538,13 +7670,18 @@ annotate_fileproc (callerdat, finfo)
struct file_info *finfo;
{
FILE *fp = NULL;
+ struct rcsbuffer *rcsbufp = NULL;
+ struct rcsbuffer rcsbuf;
char *version;
if (finfo->rcs == NULL)
return (1);
if (finfo->rcs->flags & PARTIAL)
- RCS_reparsercsfile (finfo->rcs, &fp);
+ {
+ RCS_reparsercsfile (finfo->rcs, &fp, &rcsbuf);
+ rcsbufp = &rcsbuf;
+ }
version = RCS_getversion (finfo->rcs, tag, date, force_tag_match,
(int *) NULL);
@@ -6557,7 +7694,7 @@ annotate_fileproc (callerdat, finfo)
cvs_outerr (finfo->fullname, 0);
cvs_outerr ("\n***************\n", 0);
- RCS_deltas (finfo->rcs, fp, version, RCS_ANNOTATE, (char **) NULL,
+ RCS_deltas (finfo->rcs, fp, rcsbufp, version, RCS_ANNOTATE, (char **) NULL,
(size_t) NULL, (char **) NULL, (size_t *) NULL);
free (version);
return 0;
diff --git a/gnu/usr.bin/cvs/src/rcscmds.c b/gnu/usr.bin/cvs/src/rcscmds.c
index 9a237a9be96..3086b57e10c 100644
--- a/gnu/usr.bin/cvs/src/rcscmds.c
+++ b/gnu/usr.bin/cvs/src/rcscmds.c
@@ -487,7 +487,42 @@ diff_exec (file1, file2, options, out)
char *options;
char *out;
{
- char *args = xmalloc (strlen (options) + 10);
+ char *args;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If either file1 or file2 are special files, pretend they are
+ /dev/null. Reason: suppose a file that represents a block
+ special device in one revision becomes a regular file. CVS
+ must find the `difference' between these files, but a special
+ file contains no data useful for calculating this metric. The
+ safe thing to do is to treat the special file as an empty file,
+ thus recording the regular file's full contents. Doing so will
+ create extremely large deltas at the point of transition
+ between device files and regular files, but this is probably
+ very rare anyway.
+
+ There may be ways around this, but I think they are fraught
+ with danger. -twp */
+
+ if (preserve_perms &&
+ strcmp (file1, DEVNULL) != 0 &&
+ strcmp (file2, DEVNULL) != 0)
+ {
+ struct stat sb1, sb2;
+
+ if (CVS_LSTAT (file1, &sb1) < 0)
+ error (1, errno, "cannot get file information for %s", file1);
+ if (CVS_LSTAT (file2, &sb2) < 0)
+ error (1, errno, "cannot get file information for %s", file2);
+
+ if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
+ file1 = DEVNULL;
+ if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
+ file2 = DEVNULL;
+ }
+#endif
+
+ args = xmalloc (strlen (options) + 10);
/* The first word in this string is used only for error reporting. */
sprintf (args, "diff %s", options);
call_diff_setup (args);
@@ -507,7 +542,31 @@ diff_execv (file1, file2, label1, label2, options, out)
char *options;
char *out;
{
- char *args = xmalloc (strlen (options) + 10);
+ char *args;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Pretend that special files are /dev/null for purposes of making
+ diffs. See comments in diff_exec. */
+
+ if (preserve_perms &&
+ strcmp (file1, DEVNULL) != 0 &&
+ strcmp (file2, DEVNULL) != 0)
+ {
+ struct stat sb1, sb2;
+
+ if (CVS_LSTAT (file1, &sb1) < 0)
+ error (1, errno, "cannot get file information for %s", file1);
+ if (CVS_LSTAT (file2, &sb2) < 0)
+ error (1, errno, "cannot get file information for %s", file2);
+
+ if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
+ file1 = DEVNULL;
+ if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
+ file2 = DEVNULL;
+ }
+#endif
+
+ args = xmalloc (strlen (options) + 10);
/* The first word in this string is used only for error reporting. */
/* I guess we are pretty confident that options starts with a space. */
sprintf (args, "diff%s", options);
diff --git a/gnu/usr.bin/cvs/src/server.c b/gnu/usr.bin/cvs/src/server.c
index b512845192f..06e293e0c43 100644
--- a/gnu/usr.bin/cvs/src/server.c
+++ b/gnu/usr.bin/cvs/src/server.c
@@ -1194,7 +1194,7 @@ serve_modified (arg)
}
{
- int status = change_mode (arg, mode_text);
+ int status = change_mode (arg, mode_text, 0);
free (mode_text);
if (status)
{
@@ -3355,12 +3355,13 @@ server_modtime (finfo, vers_ts)
/* See server.h for description. */
void
-server_updated (finfo, vers, updated, file_info, checksum)
+server_updated (finfo, vers, updated, mode, checksum, filebuf)
struct file_info *finfo;
Vers_TS *vers;
enum server_updated_arg4 updated;
- struct stat *file_info;
+ mode_t mode;
unsigned char *checksum;
+ struct buffer *filebuf;
{
if (noexec)
{
@@ -3379,25 +3380,43 @@ server_updated (finfo, vers, updated, file_info, checksum)
if (entries_line != NULL && scratched_file == NULL)
{
FILE *f;
- struct stat sb;
struct buffer_data *list, *last;
unsigned long size;
char size_text[80];
- if ( CVS_STAT (finfo->file, &sb) < 0)
+ if (filebuf != NULL)
{
- if (existence_error (errno))
+ size = buf_length (filebuf);
+ if (mode == (mode_t) -1)
+ error (1, 0, "\
+CVS server internal error: no mode in server_updated");
+ }
+ else
+ {
+ struct stat sb;
+
+ if ( CVS_STAT (finfo->file, &sb) < 0)
{
- /*
- * If we have a sticky tag for a branch on which the
- * file is dead, and cvs update the directory, it gets
- * a T_CHECKOUT but no file. So in this case just
- * forget the whole thing. */
- free (entries_line);
- entries_line = NULL;
- goto done;
+ if (existence_error (errno))
+ {
+ /* If we have a sticky tag for a branch on which
+ the file is dead, and cvs update the directory,
+ it gets a T_CHECKOUT but no file. So in this
+ case just forget the whole thing. */
+ free (entries_line);
+ entries_line = NULL;
+ goto done;
+ }
+ error (1, errno, "reading %s", finfo->fullname);
+ }
+ size = sb.st_size;
+ if (mode == (mode_t) -1)
+ {
+ /* FIXME: When we check out files the umask of the
+ server (set in .bashrc if rsh is in use) affects
+ what mode we send, and it shouldn't. */
+ mode = sb.st_mode;
}
- error (1, errno, "reading %s", finfo->fullname);
}
if (checksum != NULL)
@@ -3466,21 +3485,14 @@ server_updated (finfo, vers, updated, file_info, checksum)
{
char *mode_string;
- /* FIXME: When we check out files the umask of the server
- (set in .bashrc if rsh is in use) affects what mode we
- send, and it shouldn't. */
- if (file_info != NULL)
- mode_string = mode_to_string (file_info->st_mode);
- else
- mode_string = mode_to_string (sb.st_mode);
+ mode_string = mode_to_string (mode);
buf_output0 (protocol, mode_string);
buf_output0 (protocol, "\n");
free (mode_string);
}
list = last = NULL;
- size = 0;
- if (sb.st_size > 0)
+ if (size > 0)
{
/* Throughout this section we use binary mode to read the
file we are sending. The client handles any line ending
@@ -3493,11 +3505,19 @@ server_updated (finfo, vers, updated, file_info, checksum)
* might be computable somehow; using 100 here is just
* a first approximation.
*/
- && sb.st_size > 100)
+ && size > 100)
{
int status, fd, gzip_status;
pid_t gzip_pid;
+ /* Callers must avoid passing us a buffer if
+ file_gzip_level is set. We could handle this case,
+ but it's not worth it since this case never arises
+ with a current client and server. */
+ if (filebuf != NULL)
+ error (1, 0, "\
+CVS server internal error: unhandled case in server_updated");
+
fd = CVS_OPEN (finfo->file, O_RDONLY | OPEN_BINARY, 0);
if (fd < 0)
error (1, errno, "reading %s", finfo->fullname);
@@ -3520,15 +3540,14 @@ server_updated (finfo, vers, updated, file_info, checksum)
/* Prepending length with "z" is flag for using gzip here. */
buf_output0 (protocol, "z");
}
- else
+ else if (filebuf == NULL)
{
long status;
- size = sb.st_size;
f = CVS_FOPEN (finfo->file, "rb");
if (f == NULL)
error (1, errno, "reading %s", finfo->fullname);
- status = buf_read_file (f, sb.st_size, &list, &last);
+ status = buf_read_file (f, size, &list, &last);
if (status == -2)
(*protocol->memory_error) (protocol);
else if (status != 0)
@@ -3542,7 +3561,13 @@ server_updated (finfo, vers, updated, file_info, checksum)
sprintf (size_text, "%lu\n", size);
buf_output0 (protocol, size_text);
- buf_append_data (protocol, list, last);
+ if (filebuf == NULL)
+ buf_append_data (protocol, list, last);
+ else
+ {
+ buf_append_buffer (protocol, filebuf);
+ buf_free (filebuf);
+ }
/* Note we only send a newline here if the file ended with one. */
/*
@@ -3555,6 +3580,7 @@ server_updated (finfo, vers, updated, file_info, checksum)
if ((updated == SERVER_UPDATED
|| updated == SERVER_PATCHED
|| updated == SERVER_RCS_DIFF)
+ && filebuf != NULL
/* But if we are joining, we'll need the file when we call
join_file. */
&& !joining ())
@@ -5611,7 +5637,7 @@ this client does not support writing binary files to stdout");
I assume that what they are talking about can also be helped
by flushing the stream before changing the mode. */
fflush (stdout);
- oldmode = _setmode (_fileno (stdout), _O_BINARY);
+ oldmode = _setmode (_fileno (stdout), OPEN_BINARY);
if (oldmode < 0)
error (0, errno, "failed to setmode on stdout");
#endif
@@ -5626,7 +5652,7 @@ this client does not support writing binary files to stdout");
}
#ifdef USE_SETMODE_STDOUT
fflush (stdout);
- if (_setmode (_fileno (stdout), oldmode) != _O_BINARY)
+ if (_setmode (_fileno (stdout), oldmode) != OPEN_BINARY)
error (0, errno, "failed to setmode on stdout");
#endif
}
diff --git a/gnu/usr.bin/cvs/src/update.c b/gnu/usr.bin/cvs/src/update.c
index 0563eb346ad..31ee6ca2c75 100644
--- a/gnu/usr.bin/cvs/src/update.c
+++ b/gnu/usr.bin/cvs/src/update.c
@@ -42,9 +42,14 @@
#include "fileattr.h"
#include "edit.h"
#include "getline.h"
+#include "buffer.h"
+#include "hardlink.h"
static int checkout_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts,
- int adding));
+ int adding, int merging, int update_server));
+#ifdef SERVER_SUPPORT
+static void checkout_to_buffer PROTO ((void *, const char *, size_t));
+#endif
#ifdef SERVER_SUPPORT
static int patch_file PROTO ((struct file_info *finfo,
Vers_TS *vers_ts,
@@ -64,6 +69,9 @@ static int update_fileproc PROTO ((void *callerdat, struct file_info *));
static int update_filesdone_proc PROTO ((void *callerdat, int err,
char *repository, char *update_dir,
List *entries));
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+static int get_linkinfo_proc PROTO ((void *callerdat, struct file_info *));
+#endif
static void write_letter PROTO ((struct file_info *finfo, int letter));
static void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts));
@@ -79,6 +87,10 @@ static char *date = NULL;
static int rewrite_tag;
static int nonbranch;
+/* If we set the tag or date for a subdirectory, we use this to undo
+ the setting. See update_dirent_proc. */
+static char *tag_update_dir;
+
static char *join_rev1, *date_rev1;
static char *join_rev2, *date_rev2;
static int aflag = 0;
@@ -437,6 +449,32 @@ do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
else
date_rev2 = (char *) NULL;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms)
+ {
+ /* We need to do an extra recursion, bleah. It's to make sure
+ that we know as much as possible about file linkage. */
+ hardlist = getlist();
+ working_dir = xgetwd(); /* save top-level working dir */
+
+ /* FIXME-twp: the arguments to start_recursion make me dizzy. This
+ function call was copied from the update_fileproc call that
+ follows it; someone should make sure that I did it right. */
+ err = start_recursion (get_linkinfo_proc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, which, aflag, 1,
+ preload_update_dir, 1);
+ if (err)
+ return (err);
+
+ /* FIXME-twp: at this point we should walk the hardlist
+ and update the `links' field of each hardlink_info struct
+ to list the files that are linked on dist. That would make
+ it easier & more efficient to compare the disk linkage with
+ the repository linkage (a simple strcmp). */
+ }
+#endif
+
/* call the recursion processor */
err = start_recursion (update_fileproc, update_filesdone_proc,
update_dirent_proc, update_dirleave_proc, NULL,
@@ -456,6 +494,50 @@ do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
return (err);
}
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+/*
+ * The get_linkinfo_proc callback adds each file to the hardlist
+ * (see hardlink.c).
+ */
+
+static int
+get_linkinfo_proc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ char *fullpath;
+ Node *linkp;
+ struct hardlink_info *hlinfo;
+
+ /* Get the full pathname of the current file. */
+ fullpath = xmalloc (strlen(working_dir) +
+ strlen(finfo->fullname) + 2);
+ sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
+
+ /* To permit recursing into subdirectories, files
+ are keyed on the full pathname and not on the basename. */
+ linkp = lookup_file_by_inode (fullpath);
+ if (linkp == NULL)
+ {
+ /* The file isn't on disk; we are probably restoring
+ a file that was removed. */
+ return 0;
+ }
+
+ /* Create a new, empty hardlink_info node. */
+ hlinfo = (struct hardlink_info *)
+ xmalloc (sizeof (struct hardlink_info));
+
+ hlinfo->status = (Ctype) 0; /* is this dumb? */
+ hlinfo->checked_out = 0;
+ hlinfo->links = NULL;
+
+ linkp->data = (char *) hlinfo;
+
+ return 0;
+}
+#endif
+
/*
* This is the callback proc for update. It is called for each file in each
* directory by the recursion code. The current directory is the local
@@ -526,7 +608,7 @@ update_fileproc (callerdat, finfo)
#ifdef SERVER_SUPPORT
case T_PATCH: /* needs patch */
#endif
- retval = checkout_file (finfo, vers, 0);
+ retval = checkout_file (finfo, vers, 0, 0, 0);
break;
default: /* can't ever happen :-) */
@@ -629,7 +711,8 @@ update_fileproc (callerdat, finfo)
(rcs_diff_patches
? SERVER_RCS_DIFF
: SERVER_PATCHED),
- &file_info, checksum);
+ file_info.st_mode, checksum,
+ (struct buffer *) NULL);
break;
}
}
@@ -639,13 +722,7 @@ update_fileproc (callerdat, finfo)
/* Fall through. */
#endif
case T_CHECKOUT: /* needs checkout */
- retval = checkout_file (finfo, vers, 0);
-#ifdef SERVER_SUPPORT
- if (server_active && retval == 0)
- server_updated (finfo, vers,
- SERVER_UPDATED, (struct stat *) NULL,
- (unsigned char *) NULL);
-#endif
+ retval = checkout_file (finfo, vers, 0, 0, 1);
break;
case T_ADDED: /* added but not committed */
write_letter (finfo, 'A');
@@ -663,8 +740,9 @@ update_fileproc (callerdat, finfo)
if (vers->ts_user == NULL)
server_scratch_entry_only ();
server_updated (finfo, vers,
- SERVER_UPDATED, (struct stat *) NULL,
- (unsigned char *) NULL);
+ SERVER_UPDATED, (mode_t) -1,
+ (unsigned char *) NULL,
+ (struct buffer *) NULL);
}
#endif
break;
@@ -806,6 +884,26 @@ update_dirent_proc (callerdat, dir, repository, update_dir, entries)
else
{
/* otherwise, create the dir and appropriate adm files */
+
+ /* If no tag or date were specified on the command line,
+ and we're not using -A, we want the subdirectory to use
+ the tag and date, if any, of the current directory.
+ That way, update -d will work correctly when working on
+ a branch.
+
+ We use TAG_UPDATE_DIR to undo the tag setting in
+ update_dirleave_proc. If we did not do this, we would
+ not correctly handle a working directory with multiple
+ tags (and maybe we should prohibit such working
+ directories, but they work now and we shouldn't make
+ them stop working without more thought). */
+ if ((tag == NULL && date == NULL) && ! aflag)
+ {
+ ParseTag (&tag, &date, &nonbranch);
+ if (tag != NULL || date != NULL)
+ tag_update_dir = xstrdup (update_dir);
+ }
+
make_directory (dir);
Create_Admin (dir, update_dir, repository, tag, date,
/* This is a guess. We will rewrite it later
@@ -897,6 +995,27 @@ update_dirleave_proc (callerdat, dir, err, update_dir, entries)
{
FILE *fp;
+ /* If we set the tag or date for a new subdirectory in
+ update_dirent_proc, and we're now done with that subdirectory,
+ undo the tag/date setting. Note that we know that the tag and
+ date were both originally NULL in this case. */
+ if (tag_update_dir != NULL && strcmp (update_dir, tag_update_dir) == 0)
+ {
+ if (tag != NULL)
+ {
+ free (tag);
+ tag = NULL;
+ }
+ if (date != NULL)
+ {
+ free (date);
+ date = NULL;
+ }
+ nonbranch = 0;
+ free (tag_update_dir);
+ tag_update_dir = NULL;
+ }
+
/* run the update_prog if there is one */
/* FIXME: should be checking for errors from CVS_FOPEN and printing
them if not existence_error. */
@@ -1016,7 +1135,7 @@ isemptydir (dir, might_not_exist)
if (CVS_CHDIR (dir) < 0)
error (1, errno, "cannot change directory to %s", dir);
- l = Entries_Open (0);
+ l = Entries_Open (0, NULL);
files_removed = walklist (l, isremoved, 0);
Entries_Close (l);
@@ -1063,22 +1182,29 @@ scratch_file (finfo)
* Check out a file.
*/
static int
-checkout_file (finfo, vers_ts, adding)
+checkout_file (finfo, vers_ts, adding, merging, update_server)
struct file_info *finfo;
Vers_TS *vers_ts;
int adding;
+ int merging;
+ int update_server;
{
char *backup;
int set_time, retval = 0;
- int retcode = 0;
int status;
int file_is_dead;
+ struct buffer *revbuf;
- /* Solely to suppress a warning from gcc -Wall. */
backup = NULL;
+ revbuf = NULL;
- /* don't screw with backup files if we're going to stdout */
- if (!pipeout)
+ /* Don't screw with backup files if we're going to stdout, or if
+ we are the server. */
+ if (!pipeout
+#ifdef SERVER_SUPPORT
+ && ! server_active
+#endif
+ )
{
backup = xmalloc (strlen (finfo->file)
+ sizeof (CVSADM)
@@ -1088,6 +1214,7 @@ checkout_file (finfo, vers_ts, adding)
if (isfile (finfo->file))
rename_file (finfo->file, backup);
else
+ {
/* If -f/-t wrappers are being used to wrap up a directory,
then backup might be a directory instead of just a file. */
if (unlink_file_dir (backup) < 0)
@@ -1097,6 +1224,9 @@ checkout_file (finfo, vers_ts, adding)
/* FIXME: should include update_dir in message. */
error (0, errno, "error removing %s", backup);
}
+ free (backup);
+ backup = NULL;
+ }
}
file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs);
@@ -1124,22 +1254,64 @@ VERS: ", 0);
}
}
- status = RCS_checkout (vers_ts->srcfile,
- pipeout ? NULL : finfo->file,
- vers_ts->vn_rcs, vers_ts->vn_tag,
- vers_ts->options, RUN_TTY,
- (RCSCHECKOUTPROC) NULL, (void *) NULL);
+#ifdef SERVER_SUPPORT
+ if (update_server
+ && server_active
+ && ! pipeout
+ && ! file_gzip_level
+ && ! joining ()
+ && ! wrap_name_has (finfo->file, WRAP_FROMCVS))
+ {
+ revbuf = buf_nonio_initialize ((BUFMEMERRPROC) NULL);
+ status = RCS_checkout (vers_ts->srcfile, (char *) NULL,
+ vers_ts->vn_rcs, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY,
+ checkout_to_buffer, revbuf);
+ }
+ else
+#endif
+ status = RCS_checkout (vers_ts->srcfile,
+ pipeout ? NULL : finfo->file,
+ vers_ts->vn_rcs, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY,
+ (RCSCHECKOUTPROC) NULL, (void *) NULL);
}
if (file_is_dead || status == 0)
{
+ mode_t mode;
+
+ mode = (mode_t) -1;
+
if (!pipeout)
{
Vers_TS *xvers_ts;
+ if (revbuf != NULL)
+ {
+ struct stat sb;
+
+ /* FIXME: We should have RCS_checkout return the mode. */
+ if (stat (vers_ts->srcfile->path, &sb) < 0)
+ error (1, errno, "cannot stat %s",
+ vers_ts->srcfile->path);
+ mode = sb.st_mode &~ (S_IWRITE | S_IWGRP | S_IWOTH);
+ }
+
if (cvswrite
&& !file_is_dead
&& !fileattr_get (finfo->file, "_watched"))
- xchmod (finfo->file, 1);
+ {
+ if (revbuf == NULL)
+ xchmod (finfo->file, 1);
+ else
+ {
+ /* We know that we are the server here, so
+ although xchmod checks umask, we don't bother. */
+ mode |= (((mode & S_IRUSR) ? S_IWUSR : 0)
+ | ((mode & S_IRGRP) ? S_IWGRP : 0)
+ | ((mode & S_IROTH) ? S_IWOTH : 0));
+ }
+ }
{
/* A newly checked out file is never under the spell
@@ -1171,6 +1343,27 @@ VERS: ", 0);
if (strcmp (xvers_ts->options, "-V4") == 0)
xvers_ts->options[0] = '\0';
+ if (revbuf != NULL)
+ {
+ /* If we stored the file data into a buffer, then we
+ didn't create a file at all, so xvers_ts->ts_user
+ is wrong. The correct value is to have it be the
+ same as xvers_ts->ts_rcs, meaning that the working
+ file is unchanged from the RCS file.
+
+ FIXME: We should tell Version_TS not to waste time
+ statting the nonexistent file.
+
+ FIXME: Actually, I don't think the ts_user value
+ matters at all here. The only use I know of is
+ that it is printed in a trace message by
+ Server_Register. */
+
+ if (xvers_ts->ts_user != NULL)
+ free (xvers_ts->ts_user);
+ xvers_ts->ts_user = xstrdup (xvers_ts->ts_rcs);
+ }
+
(void) time (&last_register_time);
if (file_is_dead)
@@ -1179,7 +1372,7 @@ VERS: ", 0);
{
error (0, 0,
"warning: %s is not (any longer) pertinent",
- finfo->fullname);
+ finfo->fullname);
}
Scratch_Entry (finfo->entries, finfo->file);
#ifdef SERVER_SUPPORT
@@ -1226,21 +1419,29 @@ VERS: ", 0);
write_letter (finfo, 'U');
}
}
+
+#ifdef SERVER_SUPPORT
+ if (update_server && server_active)
+ server_updated (finfo, vers_ts,
+ merging ? SERVER_MERGED : SERVER_UPDATED,
+ mode, (unsigned char *) NULL, revbuf);
+#endif
}
else
{
- int old_errno = errno; /* save errno value over the rename */
-
- if (!pipeout && isfile (backup))
+ if (backup != NULL)
+ {
rename_file (backup, finfo->file);
+ free (backup);
+ backup = NULL;
+ }
- error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
- "could not check out %s", finfo->fullname);
+ error (0, 0, "could not check out %s", finfo->fullname);
- retval = retcode;
+ retval = status;
}
- if (!pipeout)
+ if (backup != NULL)
{
/* If -f/-t wrappers are being used to wrap up a directory,
then backup might be a directory instead of just a file. */
@@ -1259,6 +1460,24 @@ VERS: ", 0);
#ifdef SERVER_SUPPORT
+/* This function is used to write data from a file being checked out
+ into a buffer. */
+
+static void
+checkout_to_buffer (callerdat, data, len)
+ void *callerdat;
+ const char *data;
+ size_t len;
+{
+ struct buffer *buf = (struct buffer *) callerdat;
+
+ buf_output (buf, data, len);
+}
+
+#endif /* SERVER_SUPPORT */
+
+#ifdef SERVER_SUPPORT
+
/* This structure is used to pass information between patch_file and
patch_file_write. */
@@ -1335,6 +1554,14 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum)
free (rev);
}
+ /* If the revision is dead, let checkout_file handle it rather
+ than duplicating the processing here. */
+ if (RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs))
+ {
+ *docheckout = 1;
+ return 0;
+ }
+
backup = xmalloc (strlen (finfo->file)
+ sizeof (CVSADM)
+ sizeof (CVSPREFIX)
@@ -1649,25 +1876,32 @@ merge_file (finfo, vers)
xchmod (finfo->file, 1);
if (strcmp (vers->options, "-kb") == 0
- || wrap_merge_is_copy (finfo->file))
+ || wrap_merge_is_copy (finfo->file)
+ || special_file_mismatch (finfo, NULL, vers->vn_rcs))
{
- /* For binary files, a merge is always a conflict. We give the
+ /* For binary files, a merge is always a conflict. Same for
+ files whose permissions or linkage do not match. We give the
user the two files, and let them resolve it. It is possible
that we should require a "touch foo" or similar step before
we allow a checkin. */
- status = checkout_file (finfo, vers, 0);
+
+ /* TODO: it may not always be necessary to regard a permission
+ mismatch as a conflict. The working file and the RCS file
+ have a common ancestor `A'; if the working file's permissions
+ match A's, then it's probably safe to overwrite them with the
+ RCS permissions. Only if the working file, the RCS file, and
+ A all disagree should this be considered a conflict. But more
+ thought needs to go into this, and in the meantime it is safe
+ to treat any such mismatch as an automatic conflict. -twp */
+
#ifdef SERVER_SUPPORT
- /* Send the new contents of the file before the message. If we
- wanted to be totally correct, we would have the client write
- the message only after the file has safely been written. */
if (server_active)
- {
server_copy_file (finfo->file, finfo->update_dir,
finfo->repository, backup);
- server_updated (finfo, vers, SERVER_MERGED,
- (struct stat *) NULL, (unsigned char *) NULL);
- }
#endif
+
+ status = checkout_file (finfo, vers, 0, 1, 1);
+
/* Is there a better term than "nonmergeable file"? What we
really mean is, not something that CVS cannot or does not
want to merge (there might be an external manual or
@@ -1728,7 +1962,8 @@ merge_file (finfo, vers)
server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
backup);
server_updated (finfo, vers, SERVER_MERGED,
- (struct stat *) NULL, (unsigned char *) NULL);
+ (mode_t) -1, (unsigned char *) NULL,
+ (struct buffer *) NULL);
}
#endif
@@ -1962,8 +2197,8 @@ join_file (finfo, vers)
if (server_active)
{
server_scratch (finfo->file);
- server_updated (finfo, vers, SERVER_UPDATED, (struct stat *) NULL,
- (unsigned char *) NULL);
+ server_updated (finfo, vers, SERVER_UPDATED, (mode_t) -1,
+ (unsigned char *) NULL, (struct buffer *) NULL);
}
#endif
mrev = xmalloc (strlen (vers->vn_user) + 2);
@@ -2016,14 +2251,7 @@ join_file (finfo, vers)
/* FIXME: If checkout_file fails, we should arrange to
return a non-zero exit status. */
- status = checkout_file (finfo, xvers, 1);
-
-#ifdef SERVER_SUPPORT
- if (server_active && status == 0)
- server_updated (finfo, xvers,
- SERVER_UPDATED, (struct stat *) NULL,
- (unsigned char *) NULL);
-#endif
+ status = checkout_file (finfo, xvers, 1, 0, 1);
freevers_ts (&xvers);
@@ -2085,7 +2313,7 @@ join_file (finfo, vers)
(char *) NULL, RUN_TTY,
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
- error (1, retcode == -1 ? errno : 0,
+ error (1, 0,
"failed to check out %s file", finfo->fullname);
}
#endif
@@ -2158,9 +2386,11 @@ join_file (finfo, vers)
write_letter (finfo, 'U');
}
else if (strcmp (options, "-kb") == 0
- || wrap_merge_is_copy (finfo->file))
+ || wrap_merge_is_copy (finfo->file)
+ || special_file_mismatch (finfo, rev1, rev2))
{
- /* We are dealing with binary files, but real merging would
+ /* We are dealing with binary files, or files with a
+ permission/linkage mismatch, and real merging would
need to take place. This is a conflict. We give the user
the two files, and let them resolve it. It is possible
that we should require a "touch foo" or similar step before
@@ -2238,12 +2468,318 @@ join_file (finfo, vers)
server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
backup);
server_updated (finfo, vers, SERVER_MERGED,
- (struct stat *) NULL, (unsigned char *) NULL);
+ (mode_t) -1, (unsigned char *) NULL,
+ (struct buffer *) NULL);
}
#endif
free (backup);
}
+/*
+ * Report whether revisions REV1 and REV2 of FINFO agree on:
+ * . file ownership
+ * . permissions
+ * . major and minor device numbers
+ * . symbolic links
+ * . hard links
+ *
+ * If either REV1 or REV2 is NULL, the working copy is used instead.
+ *
+ * Return 1 if the files differ on these data.
+ */
+
+int
+special_file_mismatch (finfo, rev1, rev2)
+ struct file_info *finfo;
+ char *rev1;
+ char *rev2;
+{
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ struct stat sb;
+ RCSVers *vp;
+ Node *n;
+ uid_t rev1_uid, rev2_uid;
+ gid_t rev1_gid, rev2_gid;
+ mode_t rev1_mode, rev2_mode;
+ unsigned long dev_long;
+ dev_t rev1_dev, rev2_dev;
+ char *rev1_symlink = NULL;
+ char *rev2_symlink = NULL;
+ char *rev1_hardlinks = NULL;
+ char *rev2_hardlinks = NULL;
+ int check_uids, check_gids, check_modes;
+ int result;
+
+ /* If we don't care about special file info, then
+ don't report a mismatch in any case. */
+ if (!preserve_perms)
+ return 0;
+
+ /* When special_file_mismatch is called from No_Difference, the
+ RCS file has been only partially parsed. We must read the
+ delta tree in order to compare special file info recorded in
+ the delta nodes. (I think this is safe. -twp) */
+ if (finfo->rcs->flags & PARTIAL)
+ RCS_reparsercsfile (finfo->rcs, NULL, NULL);
+
+ check_uids = check_gids = check_modes = 1;
+
+ /* Obtain file information for REV1. If this is null, then stat
+ finfo->file and use that info. */
+ /* If a revision does not know anything about its status,
+ then presumably it doesn't matter, and indicates no conflict. */
+
+ if (rev1 == NULL)
+ {
+ if (islink (finfo->file))
+ rev1_symlink = xreadlink (finfo->file);
+ else
+ {
+ if (CVS_LSTAT (finfo->file, &sb) < 0)
+ error (1, errno, "could not get file information for %s",
+ finfo->file);
+ rev1_uid = sb.st_uid;
+ rev1_gid = sb.st_gid;
+ rev1_mode = sb.st_mode;
+ if (S_ISBLK (rev1_mode) || S_ISCHR (rev1_mode))
+ rev1_dev = sb.st_rdev;
+ }
+ rev1_hardlinks = list_files_linked_to (finfo->file);
+ }
+ else
+ {
+ n = findnode (finfo->rcs->versions, rev1);
+ vp = (RCSVers *) n->data;
+
+ n = findnode (vp->other_delta, "symlink");
+ if (n != NULL)
+ rev1_symlink = xstrdup (n->data);
+ else
+ {
+ n = findnode (vp->other_delta, "owner");
+ if (n == NULL)
+ check_uids = 0; /* don't care */
+ else
+ rev1_uid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "group");
+ if (n == NULL)
+ check_gids = 0; /* don't care */
+ else
+ rev1_gid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "permissions");
+ if (n == NULL)
+ check_modes = 0; /* don't care */
+ else
+ rev1_mode = strtoul (n->data, NULL, 8);
+
+ n = findnode (vp->other_delta, "special");
+ if (n == NULL)
+ rev1_mode |= S_IFREG;
+ else
+ {
+ /* If the size of `ftype' changes, fix the sscanf call also */
+ char ftype[16];
+ if (sscanf (n->data, "%16s %lu", ftype,
+ &dev_long) < 2)
+ error (1, 0, "%s:%s has bad `special' newphrase %s",
+ finfo->file, rev1, n->data);
+ rev1_dev = dev_long;
+ if (strcmp (ftype, "character") == 0)
+ rev1_mode |= S_IFCHR;
+ else if (strcmp (ftype, "block") == 0)
+ rev1_mode |= S_IFBLK;
+ else
+ error (0, 0, "%s:%s unknown file type `%s'",
+ finfo->file, rev1, ftype);
+ }
+
+ n = findnode (vp->other_delta, "hardlinks");
+ if (n == NULL)
+ rev1_hardlinks = xstrdup ("");
+ else
+ rev1_hardlinks = xstrdup (n->data);
+ }
+ }
+
+ /* Obtain file information for REV2. */
+ if (rev2 == NULL)
+ {
+ if (islink (finfo->file))
+ rev2_symlink = xreadlink (finfo->file);
+ else
+ {
+ if (CVS_LSTAT (finfo->file, &sb) < 0)
+ error (1, errno, "could not get file information for %s",
+ finfo->file);
+ rev2_uid = sb.st_uid;
+ rev2_gid = sb.st_gid;
+ rev2_mode = sb.st_mode;
+ if (S_ISBLK (rev2_mode) || S_ISCHR (rev2_mode))
+ rev2_dev = sb.st_rdev;
+ }
+ rev2_hardlinks = list_files_linked_to (finfo->file);
+ }
+ else
+ {
+ n = findnode (finfo->rcs->versions, rev2);
+ vp = (RCSVers *) n->data;
+
+ n = findnode (vp->other_delta, "symlink");
+ if (n != NULL)
+ rev2_symlink = xstrdup (n->data);
+ else
+ {
+ n = findnode (vp->other_delta, "owner");
+ if (n == NULL)
+ check_uids = 0; /* don't care */
+ else
+ rev2_uid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "group");
+ if (n == NULL)
+ check_gids = 0; /* don't care */
+ else
+ rev2_gid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "permissions");
+ if (n == NULL)
+ check_modes = 0; /* don't care */
+ else
+ rev2_mode = strtoul (n->data, NULL, 8);
+
+ n = findnode (vp->other_delta, "special");
+ if (n == NULL)
+ rev2_mode |= S_IFREG;
+ else
+ {
+ /* If the size of `ftype' changes, fix the sscanf call also */
+ char ftype[16];
+ if (sscanf (n->data, "%16s %lu", ftype,
+ &dev_long) < 2)
+ error (1, 0, "%s:%s has bad `special' newphrase %s",
+ finfo->file, rev2, n->data);
+ rev2_dev = dev_long;
+ if (strcmp (ftype, "character") == 0)
+ rev2_mode |= S_IFCHR;
+ else if (strcmp (ftype, "block") == 0)
+ rev2_mode |= S_IFBLK;
+ else
+ error (0, 0, "%s:%s unknown file type `%s'",
+ finfo->file, rev2, ftype);
+ }
+
+ n = findnode (vp->other_delta, "hardlinks");
+ if (n == NULL)
+ rev2_hardlinks = xstrdup ("");
+ else
+ rev2_hardlinks = xstrdup (n->data);
+ }
+ }
+
+ /* Check the user/group ownerships and file permissions, printing
+ an error for each mismatch found. Return 0 if all characteristics
+ matched, and 1 otherwise. */
+
+ result = 0;
+
+ /* Compare symlinks first, since symlinks are simpler (don't have
+ any other characteristics). */
+ if (rev1_symlink != NULL && rev2_symlink == NULL)
+ {
+ error (0, 0, "%s is a symbolic link",
+ (rev1 == NULL ? "working file" : rev1));
+ result = 1;
+ }
+ else if (rev1_symlink == NULL && rev2_symlink != NULL)
+ {
+ error (0, 0, "%s is a symbolic link",
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ else if (rev1_symlink != NULL)
+ result = (strcmp (rev1_symlink, rev2_symlink) == 0);
+ else
+ {
+ /* Compare user ownership. */
+ if (check_uids && rev1_uid != rev2_uid)
+ {
+ error (0, 0, "%s: owner mismatch between %s and %s",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+
+ /* Compare group ownership. */
+ if (check_gids && rev1_gid != rev2_gid)
+ {
+ error (0, 0, "%s: group mismatch between %s and %s",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+
+ /* Compare permissions. */
+ if (check_modes &&
+ (rev1_mode & 07777) != (rev2_mode & 07777))
+ {
+ error (0, 0, "%s: permission mismatch between %s and %s",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+
+ /* Compare device file characteristics. */
+ if ((rev1_mode & S_IFMT) != (rev2_mode & S_IFMT))
+ {
+ error (0, 0, "%s: %s and %s are different file types",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ else if (S_ISBLK (rev1_mode))
+ {
+ if (rev1_dev != rev2_dev)
+ {
+ error (0, 0, "%s: device numbers of %s and %s do not match",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ }
+
+ /* Compare hard links. */
+ if (strcmp (rev1_hardlinks, rev2_hardlinks) != 0)
+ {
+ error (0, 0, "%s: hard linkage of %s and %s do not match",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ }
+
+ if (rev1_symlink != NULL)
+ free (rev1_symlink);
+ if (rev2_symlink != NULL)
+ free (rev2_symlink);
+ if (rev1_hardlinks != NULL)
+ free (rev1_hardlinks);
+ if (rev2_hardlinks != NULL)
+ free (rev2_hardlinks);
+
+ return result;
+#else
+ return 0;
+#endif
+}
+
int
joining ()
{