From 1bef13d3ddecb5ffa01042af01ece350c24f17a8 Mon Sep 17 00:00:00 2001 From: Thorsten Lockert Date: Thu, 12 Mar 1998 07:22:51 +0000 Subject: Integrate local changes --- gnu/usr.bin/cvs/configure | 13 +- gnu/usr.bin/cvs/configure.in | 8 +- gnu/usr.bin/cvs/contrib/Makefile.in | 2 +- gnu/usr.bin/cvs/lib/memmove.c | 57 + gnu/usr.bin/cvs/src/Makefile.in | 8 +- gnu/usr.bin/cvs/src/checkout.c | 125 +- gnu/usr.bin/cvs/src/commit.c | 124 +- gnu/usr.bin/cvs/src/cvs.h | 8 +- gnu/usr.bin/cvs/src/lock.c | 12 - gnu/usr.bin/cvs/src/main.c | 2 +- gnu/usr.bin/cvs/src/mkmodules.c | 9 +- gnu/usr.bin/cvs/src/parseinfo.c | 20 + gnu/usr.bin/cvs/src/patch.c | 68 +- gnu/usr.bin/cvs/src/rcs.c | 2451 +++++++++++++++++++++++++---------- gnu/usr.bin/cvs/src/rcscmds.c | 63 +- gnu/usr.bin/cvs/src/server.c | 88 +- gnu/usr.bin/cvs/src/update.c | 650 +++++++++- 17 files changed, 2864 insertions(+), 844 deletions(-) create mode 100644 gnu/usr.bin/cvs/lib/memmove.c (limited to 'gnu/usr.bin/cvs') 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 <&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 +#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 #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 #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) + { + 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 == ';') { - *keyp = (char *) NULL; - *valp = (char *) NULL; - return (-1); + *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. */ - /* compress whitespace down to a single space */ - if (whitespace (c)) + *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. */ + + { + 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; + } + } + + /* If we've already gathered a '@' string, try to skip whitespace + and find a ';'. */ + if (c == '@') + { + while (1) + { + char n; - /* Do not include any trailing whitespace in the value. */ - if (c != ';') + 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; + } + + /* 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) + { + 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; + } + + /* See if there are any '@' strings in the value. */ + pat = memchr (start, '@', psemi - start); + + if (pat == NULL) + { + 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; - if (cur >= max) + 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) { - size_t curoff = cur - value; - expand_string (&value, &valsize, valsize + 1); - cur = value + curoff; - max = value + valsize; + 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; } - *cur++ = c; - c = getc (fp); - if (c == EOF) + /* Here PAT points to the final '@' in the string. */ + + *pat = ' '; + + ptr = pat + 1; + } + +#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) + { + 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; + } + + 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) { - *keyp = (char *) NULL; - *valp = (char *) NULL; - return (-1); + 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; } - /* terminate the string */ - if (cur >= max) + got = fread (rcsbuf->ptrend, 1, RCSBUF_BUFSIZE, rcsbuf->fp); + if (got == 0) { - size_t curoff = cur - value; - expand_string (&value, &valsize, valsize + 1); - cur = value + curoff; - max = value + valsize; + if (ferror (rcsbuf->fp)) + error (1, errno, "cannot read %s", rcsbuf->filename); + return NULL; } - *cur = '\0'; - /* if the string is empty, make it null */ - if (value && cur != value) + 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; +} + +/* 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. */ + +static RCSNode *cached_rcs; +static struct rcsbuffer cached_rcsbuf; - if (!(isdigit (c) || c == '.')) - return c; +/* Cache RCS and RCSBUF. This takes responsibility for closing + RCSBUF->FP. */ - cur = key; - max = key + keysize; - while (isdigit (c) || c == '.') +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); - 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"); - } + RCS_reparsercsfile (rcs, &fp, &rcsbuf); + else + 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. + + 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); - expand_keywords (rcs, (RCSVers *) p->data, nametag, log, loglen, + /* 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)) + { + 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: '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 () { -- cgit v1.2.3