Integrate local changes
authortholo <tholo@openbsd.org>
Tue, 30 Jan 1996 01:09:32 +0000 (01:09 +0000)
committertholo <tholo@openbsd.org>
Tue, 30 Jan 1996 01:09:32 +0000 (01:09 +0000)
13 files changed:
gnu/usr.bin/cvs/Makefile.bsd-wrapper
gnu/usr.bin/cvs/Makefile.in
gnu/usr.bin/cvs/contrib/pcl-cvs/compile-all.el [deleted file]
gnu/usr.bin/cvs/examples/comb [deleted file]
gnu/usr.bin/cvs/examples/uncom [deleted file]
gnu/usr.bin/cvs/lib/error.c [deleted file]
gnu/usr.bin/cvs/lib/error.h [deleted file]
gnu/usr.bin/cvs/src/commit.c
gnu/usr.bin/cvs/src/cvs.h
gnu/usr.bin/cvs/src/lock.c
gnu/usr.bin/cvs/src/main.c
gnu/usr.bin/cvs/src/patchlevel.h [deleted file]
gnu/usr.bin/cvs/src/server.c

index 256db80..da2104a 100644 (file)
@@ -1,4 +1,4 @@
-#      $Id: Makefile.bsd-wrapper,v 1.7 1996/01/01 23:51:27 deraadt Exp $
+#      $Id: Makefile.bsd-wrapper,v 1.8 1996/01/30 01:09:32 tholo Exp $
 
 MAN=   man/cvs.1 man/cvs.5 man/cvsbug.8 man/cvsinit.8 man/mkmodules.1
 
@@ -8,10 +8,10 @@ all:  config.status
 .FORCE:        .IGNORE
 
 config: .FORCE
-       sh ${.CURDIR}/configure --prefix=/usr
+       sh ${.CURDIR}/configure --prefix=/usr --with-krb4=/usr
 
 config.status:
-       sh ${.CURDIR}/configure --prefix=/usr
+       sh ${.CURDIR}/configure --prefix=/usr --with-krb4=/usr
 
 install: maninstall
        ${MAKE} prefix=${DESTDIR}/usr infodir=${DESTDIR}/usr/share/info \
index 6509798..2f00165 100644 (file)
@@ -93,6 +93,7 @@ DISTFILES = \
        BUGS MINOR-BUGS FAQ \
        ChangeLog NEWS ChangeLog.zoo \
        configure configure.in stamp-h.in config.h.in Makefile.in acconfig.h \
+       config.sub config.guess \
        cvs-format.el mkinstalldirs install-sh cvsinit.sh \
        cvsnt.mak \
        .cvsignore
@@ -101,7 +102,7 @@ PROGS = cvsinit
 
 # Subdirectories to run make in for the primary targets.
 INSTALL_MAN=man
-SUBDIRS = lib src ${INSTALL_MAN} doc contrib examples windows-NT
+SUBDIRS = lib src ${INSTALL_MAN} doc contrib examples windows-NT os2 macintosh
 # Only make TAGS/tags files in these directories, in this order
 TSUBDIRS= src lib
 
@@ -199,14 +200,17 @@ saber:
 
 .PHONY: check
 check:
+       cd lib ; $(MAKE) $(FLAGS_TO_PASS)
        cd src ; $(MAKE) $(FLAGS_TO_PASS) check
 
 .PHONY: remotecheck
 remotecheck:
+       cd lib ; $(MAKE) $(FLAGS_TO_PASS)
        cd src ; $(MAKE) $(FLAGS_TO_PASS) remotecheck
 
 .PHONY: installcheck
 installcheck:
+       cd lib ; $(MAKE) $(FLAGS_TO_PASS)
        cd src ; $(MAKE) $(FLAGS_TO_PASS) installcheck
 
 .PHONY: lint
@@ -216,6 +220,7 @@ lint:
 .PHONY: dist
 GZIP=gzip --best
 GZIP_EXT=.gz
+TAR_VERBOSE=
 dist:
        echo > .fname \
          cvs-`sed < $(srcdir)/src/version.c \
@@ -230,7 +235,7 @@ dist:
            ${MAKE} dist-dir DISTDIR="$${DISTDIR}" \
          ); \
        done
-       tar chvf - `cat .fname` | ${GZIP} > "`cat .fname`.tar${GZIP_EXT}"
+       tar chf${TAR_VERBOSE} - `cat .fname` | ${GZIP} > "`cat .fname`.tar${GZIP_EXT}"
        rm -rf `cat .fname` .fname
 
 .PHONY: dist-dir
diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/compile-all.el b/gnu/usr.bin/cvs/contrib/pcl-cvs/compile-all.el
deleted file mode 100644 (file)
index 6563277..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-;;;; @(#) Id: compile-all.el,v 1.11 1993/05/31 18:40:25 ceder Exp 
-;;;; This file byte-compiles all .el files in pcl-cvs release 1.05.
-;;;;
-;;;; Copyright (C) 1991 Inge Wallin
-;;;;
-;;;; This file was once upon a time part of Elib, but have since been
-;;;; modified by Per Cederqvist.
-;;;;
-;;;; GNU Elib is free software; you can redistribute it and/or modify
-;;;; it under the terms of the GNU General Public License as published by
-;;;; the Free Software Foundation; either version 1, or (at your option)
-;;;; any later version.
-;;;;
-;;;; GNU Elib is distributed in the hope that it will be useful,
-;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-;;;; GNU General Public License for more details.
-;;;;
-;;;; You should have received a copy of the GNU General Public License
-;;;; along with GNU Emacs; see the file COPYING.  If not, write to
-;;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
-;;;;
-
-
-(setq files-to-compile '("pcl-cvs" "pcl-cvs-lucid"))
-
-
-(defun compile-file-if-necessary (file)
-  "Compile FILE if necessary.
-
-This is done if FILE.el is newer than FILE.elc or if FILE.elc doesn't exist."
-  (let ((el-name (concat file ".el"))
-       (elc-name (concat file ".elc")))
-    (if (or (not (file-exists-p elc-name))
-           (file-newer-than-file-p el-name elc-name))
-       (progn
-         (message (format "Byte-compiling %s..." el-name))
-         (byte-compile-file el-name)))))
-
-
-(defun compile-pcl-cvs ()
-  "Byte-compile all uncompiled files of pcl-cvs."
-
-  (interactive)
-
-  ;; Be sure to have . in load-path since a number of files
-  ;; depend on other files and we always want the newer one even if
-  ;; a previous version of pcl-cvs exists.
-  (let ((load-path (append '(".") load-path)))
-
-    (mapcar (function compile-file-if-necessary)
-           files-to-compile)))
diff --git a/gnu/usr.bin/cvs/examples/comb b/gnu/usr.bin/cvs/examples/comb
deleted file mode 100644 (file)
index 8602856..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/sh
-#
-# Combine a directory into a single tar package.
-#
-# This script is always called with the current directory set to
-# where the file to be combined exists. but i may get called with a
-# path to where cvs first started executing. (this probably should be
-# fixed in cvs) so strip out all of the directory information. The
-# first sed expression will only work if the path has a leading /
-# if it doesn't the one in the if statement will work.
-DIRNAME=`echo $1 | sed -e "s|/.*/||g"`
-if [ ! -d $DIRNAME ] ; then
-      DIRNAME=`echo $1 | sed -e "s|.*/||g"`
-fi
-#
-# Now tar up the directory but we now will only get a relative path
-# even if the user did a cvs commit . at the top.
-#
-gnutar --preserve --sparse -cf - $DIRNAME | gzip --no-name --best -c > $2
diff --git a/gnu/usr.bin/cvs/examples/uncom b/gnu/usr.bin/cvs/examples/uncom
deleted file mode 100644 (file)
index b7ab277..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-#
-# extract the combined package (created with comb)
-# into the individual components.
-
-# move the file to a new name with an extension
-rm -rf $1.cvswrap
-mv $1 $1.cvswrap
-
-# untar the file
-
-if `gzip -t $1.cvswrap > /dev/null 2>&1`
-then
-       gzcat -d $1.cvswrap | gnutar --preserve --sparse -x -f -
-else
-       gnutar --preserve --sparse -x -f $1.cvswrap
-fi
-
-# remove the original
-rm -rf $1.cvswrap
diff --git a/gnu/usr.bin/cvs/lib/error.c b/gnu/usr.bin/cvs/lib/error.c
deleted file mode 100644 (file)
index 72f3826..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-/* error.c -- error handler for noninteractive utilities
-   Copyright (C) 1990-1992 Free Software Foundation, Inc.
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
-
-/* David MacKenzie */
-/* Brian Berliner added support for CVS */
-
-#ifndef lint
-static char rcsid[] = "$CVSid: @(#)error.c 1.13 94/09/30 $";
-#endif /* not lint */
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <stdio.h>
-
-#ifdef CVS_SUPPORT
-/* If non-zero, error will use the CVS protocol to report error
-   messages.  This will only be set in the CVS server parent process;
-   most other code is run via do_cvs_command, which forks off a child
-   process and packages up its stderr in the protocol.
-
-   This really is ugly (why should lib/error.c know about the
-   protocol??), but it's better than a mass munging of all the calls
-   to error in modules.c.  */
-int error_use_protocol; 
-#endif
-
-#ifdef HAVE_VPRINTF
-
-#if __STDC__
-#include <stdarg.h>
-#define VA_START(args, lastarg) va_start(args, lastarg)
-#else
-#include <varargs.h>
-#define VA_START(args, lastarg) va_start(args)
-#endif
-
-#else
-
-#ifdef HAVE_DOPRNT
-#define va_alist args
-#define va_dcl int args;
-#else
-#define va_alist a1, a2, a3, a4, a5, a6, a7, a8
-#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;
-#endif
-
-#endif
-
-#if STDC_HEADERS
-#include <stdlib.h>
-#include <string.h>
-#else
-#if __STDC__
-void exit(int status);
-#else
-void exit ();
-#endif /* __STDC__ */
-#endif
-
-extern char *strerror ();
-
-typedef void (*fn_returning_void) ();
-
-/* Function to call before exiting.  */
-static fn_returning_void cleanup_fn;
-
-fn_returning_void
-error_set_cleanup (arg)
-     fn_returning_void arg;
-{
-  fn_returning_void retval = cleanup_fn;
-  cleanup_fn = arg;
-  return retval;
-}
-
-/* Print the program name and error message MESSAGE, which is a printf-style
-   format string with optional args.
-   If ERRNUM is nonzero, print its corresponding system error message.
-   Exit with status STATUS if it is nonzero. */
-/* VARARGS */
-void
-#if defined (HAVE_VPRINTF) && __STDC__
-error (int status, int errnum, char *message, ...)
-#else
-error (status, errnum, message, va_alist)
-     int status;
-     int errnum;
-     char *message;
-     va_dcl
-#endif
-{
-  FILE *out = stderr;
-  extern char *program_name;
-#ifdef CVS_SUPPORT
-  extern char *command_name;
-#endif
-#ifdef HAVE_VPRINTF
-  va_list args;
-#endif
-
-#ifdef CVS_SUPPORT
-  if (error_use_protocol)
-    {
-      out = stdout;
-      printf ("E ");
-    }
-#endif
-
-#ifdef CVS_SUPPORT
-  if (command_name && *command_name)
-    if (status)
-      fprintf (out, "%s [%s aborted]: ", program_name, command_name);
-    else
-      fprintf (out, "%s %s: ", program_name, command_name);
-  else
-    fprintf (out, "%s: ", program_name);
-#else
-  fprintf (out, "%s: ", program_name);
-#endif
-#ifdef HAVE_VPRINTF
-  VA_START (args, message);
-  vfprintf (out, message, args);
-  va_end (args);
-#else
-#ifdef HAVE_DOPRNT
-  _doprnt (message, &args, out);
-#else
-  fprintf (out, message, a1, a2, a3, a4, a5, a6, a7, a8);
-#endif
-#endif
-  if (errnum)
-    fprintf (out, ": %s", strerror (errnum));
-  putc ('\n', out);
-  fflush (out);
-  if (status)
-    {
-      if (cleanup_fn)
-       (*cleanup_fn) ();
-      exit (status);
-    }
-}
-
-#ifdef CVS_SUPPORT
-
-/* Print the program name and error message MESSAGE, which is a printf-style
-   format string with optional args to the file specified by FP.
-   If ERRNUM is nonzero, print its corresponding system error message.
-   Exit with status STATUS if it is nonzero. */
-/* VARARGS */
-void
-#if defined (HAVE_VPRINTF) && __STDC__
-fperror (FILE *fp, int status, int errnum, char *message, ...)
-#else
-fperror (fp, status, errnum, message, va_alist)
-     FILE *fp;
-     int status;
-     int errnum;
-     char *message;
-     va_dcl
-#endif
-{
-  extern char *program_name;
-#ifdef HAVE_VPRINTF
-  va_list args;
-#endif
-
-  fprintf (fp, "%s: ", program_name);
-#ifdef HAVE_VPRINTF
-  VA_START (args, message);
-  vfprintf (fp, message, args);
-  va_end (args);
-#else
-#ifdef HAVE_DOPRNT
-  _doprnt (message, &args, fp);
-#else
-  fprintf (fp, message, a1, a2, a3, a4, a5, a6, a7, a8);
-#endif
-#endif
-  if (errnum)
-    fprintf (fp, ": %s", strerror (errnum));
-  putc ('\n', fp);
-  fflush (fp);
-  if (status)
-    {
-      if (cleanup_fn)
-       (*cleanup_fn) ();
-      exit (status);
-    }
-}
-
-#endif /* CVS_SUPPORT */
diff --git a/gnu/usr.bin/cvs/lib/error.h b/gnu/usr.bin/cvs/lib/error.h
deleted file mode 100644 (file)
index a963e11..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/* error.h -- declaration for error-reporting function
-   Copyright (C) 1995 Software Foundation, Inc.
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
-
-#ifndef _error_h_
-#define _error_h_
-
-#ifndef __attribute__
-/* This feature is available in gcc versions 2.5 and later.  */
-# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__
-#  define __attribute__(Spec) /* empty */
-# endif
-/* The __-protected variants of `format' and `printf' attributes
-   are accepted by gcc versions 2.6.4 (effectively 2.7) and later.  */
-# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
-#  define __format__ format
-#  define __printf__ printf
-# endif
-#endif
-
-#if __STDC__
-void error (int, int, const char *, ...) \
-  __attribute__ ((__format__ (__printf__, 3, 4)));
-#else
-void error ();
-#endif
-
-/* This variable is incremented each time `error' is called.  */
-extern unsigned int error_message_count;
-
-#ifdef CVS_SUPPORT
-/* If non-zero, error will use the CVS protocol to report error
-   messages.  This will only be set in the CVS server parent process;
-   most other code is run via do_cvs_command, which forks off a child
-   process and packages up its stderr in the protocol.  */
-extern int error_use_protocol;
-#endif
-
-#endif /* _error_h_ */
index c37ed71..1973e0c 100644 (file)
@@ -15,6 +15,9 @@
  */
 
 #include "cvs.h"
+#include "getline.h"
+#include "edit.h"
+#include "fileattr.h"
 
 #ifndef lint
 static const char rcsid[] = "$CVSid: @(#)commit.c 1.101 94/10/07 $";
@@ -26,7 +29,7 @@ static int check_fileproc PROTO((char *file, char *update_dir, char *repository,
                           List * entries, List * srcfiles));
 static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir));
 static int checkaddfile PROTO((char *file, char *repository, char *tag,
-                        List *srcfiles)); 
+                              char *options, List *srcfiles)); 
 static Dtype commit_direntproc PROTO((char *dir, char *repos, char *update_dir));
 static int commit_dirleaveproc PROTO((char *dir, int err, char *update_dir));
 static int commit_fileproc PROTO((char *file, char *update_dir, char *repository,
@@ -36,9 +39,7 @@ static int finaladd PROTO((char *file, char *revision, char *tag,
                           char *options, char *update_dir,
                           char *repository, List *entries));
 static int findmaxrev PROTO((Node * p, void *closure));
-static int fsortcmp PROTO((const Node * p, const Node * q));
 static int lock_RCS PROTO((char *user, char *rcs, char *rev, char *repository));
-static int lock_filesdoneproc PROTO((int err, char *repository, char *update_dir));
 static int lockrcsfile PROTO((char *file, char *repository, char *rev));
 static int precommit_list_proc PROTO((Node * p, void *closure));
 static int precommit_proc PROTO((char *repository, char *filter));
@@ -73,7 +74,6 @@ static char *tag;
 static char *write_dirtag;
 static char *logfile;
 static List *mulist;
-static List *locklist;
 static char *message;
 
 static const char *const commit_usage[] =
@@ -89,6 +89,113 @@ static const char *const commit_usage[] =
     NULL
 };
 
+#ifdef CLIENT_SUPPORT
+struct find_data {
+    List *ulist;
+    int argc;
+    char **argv;
+};
+
+/* Pass as a static until we get around to fixing start_recursion to
+   pass along a void * where we can stash it.  */
+struct find_data *find_data_static;
+
+static int find_fileproc PROTO ((char *, char *, char *, List *, List *));
+
+/* Machinery to find out what is modified, added, and removed.  It is
+   possible this should be broken out into a new client_classify function;
+   merging it with classify_file is almost sure to be a mess, though,
+   because classify_file has all kinds of repository processing.  */
+static int
+find_fileproc (file, update_dir, repository, entries, srcfiles)
+    char *file;
+    char *update_dir;
+    char *repository;
+    List *entries;
+    List *srcfiles;
+{
+    Vers_TS *vers;
+    enum classify_type status;
+    Node *node;
+    struct find_data *args = find_data_static;
+    char *fullname;
+
+    fullname = xmalloc (strlen (update_dir) + strlen (file) + 10);
+    fullname[0] = '\0';
+    if (update_dir[0] != '\0')
+    {
+       strcat (fullname, update_dir);
+       strcat (fullname, "/");
+    }
+    strcat (fullname, file);
+
+    vers = Version_TS ((char *)NULL, (char *)NULL, (char *)NULL,
+                      (char *)NULL,
+                      file, 0, 0, entries, (List *)NULL);
+    if (vers->ts_user == NULL
+       && vers->vn_user != NULL
+       && vers->vn_user[0] == '-')
+       status = T_REMOVED;
+    else if (vers->ts_user == NULL
+            && vers->vn_user == NULL)
+    {
+       error (0, 0, "nothing known about `%s'", fullname);
+       free (fullname);
+       return 1;
+    }
+    else if (vers->ts_user != NULL
+            && vers->vn_user != NULL
+            && vers->vn_user[0] == '0')
+       status = T_ADDED;
+    else if (vers->ts_user != NULL
+            && vers->ts_rcs != NULL
+            && strcmp (vers->ts_user, vers->ts_rcs) != 0)
+       status = T_MODIFIED;
+    else
+    {
+       /* This covers unmodified files, as well as a variety of other cases
+          (e.g. "touch foo", "cvs ci foo").  FIXME: we probably should be
+          printing a message and returning 1 for many of those cases (but
+          I'm not sure exactly which ones).  */
+       free (fullname);
+       return 0;
+    }
+
+    node = getnode ();
+    node->key = xmalloc (strlen (update_dir) + strlen (file) + 8);
+    node->key[0] = '\0';
+    if (update_dir[0] != '\0')
+    {
+       strcpy (node->key, update_dir);
+       strcat (node->key, "/");
+    }
+    strcat (node->key, file);
+
+    node->type = UPDATE;
+    node->delproc = update_delproc;
+    node->data = (char *) status;
+    (void)addnode (args->ulist, node);
+
+    ++args->argc;
+
+    free (fullname);
+
+    return 0;
+}
+
+static int copy_ulist PROTO ((Node *, void *));
+
+static int
+copy_ulist (node, data)
+    Node *node;
+    void *data;
+{
+    struct find_data *args = (struct find_data *)data;
+    args->argv[args->argc++] = node->key;
+    return 0;
+}
+#endif /* CLIENT_SUPPORT */
+
 int
 commit (argc, argv)
     int argc;
@@ -207,24 +314,49 @@ commit (argc, argv)
 #ifdef CLIENT_SUPPORT
     if (client_active) 
     {
+       struct find_data find_args;
+
+       ign_setup ();
+
+       /* Note that we don't do ignore file processing here, and we
+          don't call ignore_files.  This means that we won't print "?
+          foo" for stray files.  Sounds OK, the doc only promises
+          that update does that.  */
+       find_args.ulist = getlist ();
+       find_args.argc = 0;
+       find_data_static = &find_args;
+       err = start_recursion (find_fileproc, (FILESDONEPROC) NULL,
+                               (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+                               argc, argv, local, W_LOCAL, 0, 0,
+                               (char *)NULL, 0, 0);
+       if (err)
+           error (1, 0, "correct above errors first!");
+
+       if (find_args.argc == 0)
+           return 0;
+
+       /* Now we keep track of which files we actually are going to
+          operate on, and only work with those files in the future.
+          This saves time--we don't want to search the file system
+          of the working directory twice.  */
+       find_args.argv = (char **) xmalloc (find_args.argc * sizeof (char **));
+       find_args.argc = 0;
+       walklist (find_args.ulist, copy_ulist, &find_args);
+
        /*
-        * Do this now; don't ask for a log message if we can't talk to the
-        * server.  But if there is a syntax error in the options, give
-        * an error message without connecting.
-        */
+        * Do this before calling do_editor; don't ask for a log
+        * message if we can't talk to the server.  But do it after we
+        * have made the checks that we can locally (to more quickly
+        * catch syntax errors, the case where no files are modified,
+        * added or removed, etc.).  */
        start_server ();
-       
-       ign_setup ();
 
        /*
         * We do this once, not once for each directory as in normal CVS.
         * The protocol is designed this way.  This is a feature.
-        *
-        * We could provide the lists of changed, modified, etc. files,
-        * however.  Our failure to do so is just laziness, not design.
         */
        if (use_editor)
-           do_editor (".", &message, (char *)NULL, (List *)NULL);
+           do_editor (".", &message, (char *)NULL, find_args.ulist);
 
        /* We always send some sort of message, even if empty.  */
        option_with_arg ("-m", message);
@@ -237,32 +369,29 @@ commit (argc, argv)
            send_arg("-n");
        option_with_arg ("-r", tag);
 
-       send_files (argc, argv, local, 0);
+       /* Sending only the names of the files which were modified, added,
+          or removed means that the server will only do an up-to-date
+          check on those files.  This is different from local CVS and
+          previous versions of client/server CVS, but it probably is a Good
+          Thing, or at least Not Such A Bad Thing.  */
+       send_file_names (find_args.argc, find_args.argv);
+       send_files (find_args.argc, find_args.argv, local, 0);
 
-       if (fprintf (to_server, "ci\n") < 0)
-           error (1, errno, "writing to server");
+       send_to_server ("ci\012", 0);
        return get_responses_and_close ();
     }
 #endif
 
+    if (tag != NULL)
+       tag_check_valid (tag, argc, argv, local, aflag, "");
+
     /* XXX - this is not the perfect check for this */
     if (argc <= 0)
        write_dirtag = tag;
 
     wrap_setup ();
 
-    /*
-     * Run the recursion processor to find all the dirs to lock and lock all
-     * the dirs
-     */
-    locklist = getlist ();
-    err = start_recursion ((int (*) ()) NULL, lock_filesdoneproc,
-                          (Dtype (*) ()) NULL, (int (*) ()) NULL, argc,
-                          argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0,
-                          0);
-    sortlist (locklist, fsortcmp);
-    if (Writer_Lock (locklist) != 0)
-       error (1, 0, "lock failed - giving up");
+    lock_tree_for_write (argc, argv, local, aflag);
 
     /*
      * Set up the master update list
@@ -273,12 +402,12 @@ commit (argc, argv)
      * Run the recursion processor to verify the files are all up-to-date
      */
     err = start_recursion (check_fileproc, check_filesdoneproc,
-                          check_direntproc, (int (*) ()) NULL, argc,
+                          check_direntproc, (DIRLEAVEPROC) NULL, argc,
                           argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1,
                           0);
     if (err)
     {
-       Lock_Cleanup ();
+       lock_tree_cleanup ();
        error (1, 0, "correct above errors first!");
     }
 
@@ -294,41 +423,8 @@ commit (argc, argv)
     /*
      * Unlock all the dirs and clean up
      */
-    Lock_Cleanup ();
+    lock_tree_cleanup ();
     dellist (&mulist);
-    dellist (&locklist);
-    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));
-}
-
-/*
- * Create a list of repositories to lock
- */
-/* ARGSUSED */
-static int
-lock_filesdoneproc (err, repository, update_dir)
-    int err;
-    char *repository;
-    char *update_dir;
-{
-    Node *p;
-
-    p = getnode ();
-    p->type = LOCK;
-    p->key = xstrdup (repository);
-    /* FIXME-KRP: this error condition should not simply be passed by. */
-    if (p->key == NULL || addnode (locklist, p) != 0)
-       freenode (p);
     return (err);
 }
 
@@ -542,10 +638,10 @@ check_fileproc (file, update_dir, repository, entries, srcfiles)
                 * If the timestamps differ, look for Conflict indicators
                 * in the file to see if we should block the commit anyway
                 */
-               run_setup ("%s -s", GREP);
+               run_setup ("%s", GREP);
                run_arg (RCS_MERGE_PAT);
                run_arg (file);
-               retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+               retcode = run_exec (RUN_TTY, DEVNULL, RUN_TTY, RUN_REALLY);
                    
                if (retcode == -1)
                {
@@ -862,7 +958,8 @@ commit_fileproc (file, update_dir, repository, entries, srcfiles)
     }
     else if (ci->status == T_ADDED)
     {
-       if (checkaddfile (file, repository, ci->tag, srcfiles) != 0)
+       if (checkaddfile (file, repository, ci->tag, ci->options,
+                         srcfiles) != 0)
        {
            fixaddfile (file, repository);
            err = 1;
@@ -941,6 +1038,10 @@ commit_fileproc (file, update_dir, repository, entries, srcfiles)
 #endif
     }
 
+    /* Clearly this is right for T_MODIFIED.  I haven't thought so much
+       about T_ADDED or T_REMOVED.  */
+    notify_do ('C', file, getcaller (), NULL, NULL, repository);
+
 out:
     if (err != 0)
     {
@@ -985,18 +1086,23 @@ commit_filesdoneproc (err, repository, update_dir)
 
     if (err == 0 && run_module_prog)
     {
-       char *cp;
        FILE *fp;
-       char line[MAXLINELEN];
-       char *repository;
 
-       /* It is not an error if Checkin.prog does not exist.  */
        if ((fp = fopen (CVSADM_CIPROG, "r")) != NULL)
        {
-           if (fgets (line, sizeof (line), fp) != NULL)
+           char *line;
+           int line_length;
+           size_t line_chars_allocated;
+           char *repository;
+
+           line = NULL;
+           line_chars_allocated = 0;
+           line_length = getline (&line, &line_chars_allocated, fp);
+           if (line_length > 0)
            {
-               if ((cp = strrchr (line, '\n')) != NULL)
-                   *cp = '\0';
+               /* Remove any trailing newline.  */
+               if (line[line_length - 1] == '\n')
+                   line[--line_length] = '\0';
                repository = Name_Repository ((char *) NULL, update_dir);
                run_setup ("%s %s", line, repository);
                (void) printf ("%s %s: Executing '", program_name,
@@ -1006,7 +1112,21 @@ commit_filesdoneproc (err, repository, update_dir)
                (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
                free (repository);
            }
-           (void) fclose (fp);
+           else
+           {
+               if (ferror (fp))
+                   error (0, errno, "warning: error reading %s",
+                          CVSADM_CIPROG);
+           }
+           if (line != NULL)
+               free (line);
+           if (fclose (fp) < 0)
+               error (0, errno, "warning: cannot close %s", CVSADM_CIPROG);
+       }
+       else
+       {
+           if (! existence_error (errno))
+               error (0, errno, "warning: cannot open %s", CVSADM_CIPROG);
        }
     }
 
@@ -1191,7 +1311,7 @@ remove_file (file, repository, tag, message, entries, srcfiles)
        {
            /* no revision exists on this branch.  use the previous
               revision but do not lock. */
-           corev = RCS_gettag (rcsfile, tag, 1);
+           corev = RCS_gettag (rcsfile, tag, 1, 0);
            prev_rev = xstrdup(rev);
            lockflag = "";
        } else
@@ -1287,7 +1407,7 @@ remove_file (file, repository, tag, message, entries, srcfiles)
                      strlen(file) +
                      sizeof(RCSEXT) + 1);
        (void) sprintf (tmp, "%s/%s", repository, CVSATTIC);
-       omask = umask (2);
+       omask = umask (cvsumask);
        (void) CVS_MKDIR (tmp, 0777);
        (void) umask (omask);
        (void) sprintf (tmp, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
@@ -1349,8 +1469,6 @@ finaladd (file, rev, tag, options, update_dir, repository, entries)
                   message, entries);
     if (ret == 0)
     {
-       (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_OPT);
-       (void) unlink_file (tmp);
        (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_LOG);
        (void) unlink_file (tmp);
     }
@@ -1427,14 +1545,13 @@ fixbranch (file, repository, branch)
  */
 
 static int
-checkaddfile (file, repository, tag, srcfiles)
+checkaddfile (file, repository, tag, options, srcfiles)
     char *file;
     char *repository;
     char *tag;
+    char *options;
     List *srcfiles;
 {
-    FILE *fp;
-    char *cp;
     char rcs[PATH_MAX];
     char fname[PATH_MAX];
     mode_t omask;
@@ -1447,7 +1564,7 @@ checkaddfile (file, repository, tag, srcfiles)
     if (tag)
     {
        (void) sprintf(rcs, "%s/%s", repository, CVSATTIC);
-       omask = umask (2);
+       omask = umask (cvsumask);
        if (CVS_MKDIR (rcs, 0777) != 0 && errno != EEXIST)
            error (1, errno, "cannot make directory `%s'", rcs);;
        (void) umask (omask);
@@ -1492,7 +1609,7 @@ checkaddfile (file, repository, tag, srcfiles)
        }
 
        rcsfile = (RCSNode *) p->data;
-       rev = RCS_getversion (rcsfile, tag, NULL, 1);
+       rev = RCS_getversion (rcsfile, tag, NULL, 1, 0);
        /* and lock it */
        if (lock_RCS (file, rcs, rev, repository)) {
            error (0, 0, "cannot lock `%s'.", rcs);
@@ -1512,26 +1629,9 @@ checkaddfile (file, repository, tag, srcfiles)
        if (isfile (fname))
            run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG);
 
-       (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT);
-       fp = fopen (fname, "r");
-       /* If the file does not exist, no big deal.  In particular, the
-          server does not (yet at least) create CVSEXT_OPT files.  */
-       if (fp == NULL)
-       {
-           if (errno != ENOENT)
-               error (1, errno, "cannot open %s", fname);
-       }
-       else
-       {
-           while (fgets (fname, sizeof (fname), fp) != NULL)
-           {
-               if ((cp = strrchr (fname, '\n')) != NULL)
-                   *cp = '\0';
-               if (*fname)
-                   run_arg (fname);
-           }
-           (void) fclose (fp);
-       }
+       /* Set RCS keyword expansion options.  */
+       if (options && options[0] == '-' && options[1] == 'k')
+           run_arg (options);
        run_arg (rcs);
        if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
        {
@@ -1546,21 +1646,24 @@ checkaddfile (file, repository, tag, srcfiles)
        to create a dead revision on the trunk.  */
     if (tag && newfile)
     {
-       char tmp[PATH_MAX];
+       char *tmp;
        
        /* move the new file out of the way. */
        (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
        rename_file (file, fname);
        copy_file (DEVNULL, file);
-       
+
+       tmp = xmalloc (strlen (file) + strlen (tag) + 80);
        /* commit a dead revision. */
-       (void) sprintf (tmp, "-mfile %s was initially added on branch %s.", file, tag);
+       (void) sprintf (tmp, "-mfile %s was initially added on branch %s.",
+                       file, tag);
 #ifdef DEATH_STATE
        run_setup ("%s%s -q -f -sdead", Rcsbin, RCS_CI);
 #else
        run_setup ("%s%s -q -K", Rcsbin, RCS_CI);
 #endif
        run_arg (tmp);
+       free (tmp);
        run_arg (rcs);
        if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
        {
@@ -1598,7 +1701,7 @@ checkaddfile (file, repository, tag, srcfiles)
            char *head;
            char *magicrev;
            
-           head = RCS_getversion (rcsfile, NULL, NULL, 0);
+           head = RCS_getversion (rcsfile, NULL, NULL, 0, 0);
            magicrev = RCS_magicrev (rcsfile, head);
            if ((retcode = RCS_settag(rcs, tag, magicrev)) != 0)
            {
@@ -1642,16 +1745,9 @@ checkaddfile (file, repository, tag, srcfiles)
 #else /* No DEATH_SUPPORT */
     run_setup ("%s%s -i", Rcsbin, RCS);
     run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG);
-    (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT);
-    fp = open_file (fname, "r");
-    while (fgets (fname, sizeof (fname), fp) != NULL)
-    {
-       if ((cp = strrchr (fname, '\n')) != NULL)
-           *cp = '\0';
-       if (*fname)
-           run_arg (fname);
-    }
-    (void) fclose (fp);
+    /* Set RCS keyword expansion options.  */
+    if (options && options[0] == '-' && options[1] == 'k')
+       run_arg (options);
     run_arg (rcs);
     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
     {
@@ -1661,6 +1757,8 @@ checkaddfile (file, repository, tag, srcfiles)
     }
 #endif /* No DEATH_SUPPORT */
 
+    fileattr_newfile (file);
+
     fix_rcs_modes (rcs, file);
     return (0);
 }
index 50bef89..e9b0652 100644 (file)
 #ifdef _AIX
  #pragma alloca
 #else /* not _AIX */
+#ifdef ALLOCA_IN_STDLIB
+ /* then we need do nothing */
+#else
 char *alloca ();
+#endif /* not ALLOCA_IN_STDLIB */
 #endif /* not _AIX */
 #endif /* not HAVE_ALLOCA_H */
 #endif /* not __GNUC__ */
@@ -52,6 +56,11 @@ char *alloca ();
 
 #include <stdio.h>
 
+/* Under OS/2, <stdio.h> doesn't define popen()/pclose(). */
+#ifdef USE_OWN_POPEN
+#include "popen.h"
+#endif
+
 #ifdef STDC_HEADERS
 #include <stdlib.h>
 #else
@@ -75,7 +84,7 @@ extern char *getenv();
 char *strerror ();
 #endif
 
-#include <fnmatch.h>           /* This is supposed to be available on Posix systems */
+#include <fnmatch.h> /* This is supposed to be available on Posix systems */
 
 #include <ctype.h>
 #include <pwd.h>
@@ -150,6 +159,21 @@ extern int errno;
 #define        CVSADM_CIPROG   "CVS/Checkin.prog"
 #define        CVSADM_UPROG    "CVS/Update.prog"
 #define        CVSADM_TAG      "CVS/Tag"
+#define CVSADM_NOTIFY  "CVS/Notify"
+#define CVSADM_NOTIFYTMP "CVS/Notify.tmp"
+/* A directory in which we store base versions of files we currently are
+   editing with "cvs edit".  */
+#define CVSADM_BASE     "CVS/Base"
+
+/* This is the special directory which we use to store various extra
+   per-directory information in the repository.  It must be the same as
+   CVSADM to avoid creating a new reserved directory name which users cannot
+   use, but is a separate #define because if anyone changes it (which I don't
+   recommend), one needs to deal with old, unconverted, repositories.
+   
+   See fileattr.h for details about file attributes, the only thing stored
+   in CVSREP currently.  */
+#define CVSREP "CVS"
 
 /*
  * Definitions for the CVSROOT Administrative directory and the files it
@@ -165,9 +189,13 @@ extern int errno;
 #define CVSROOTADM_TAGINFO      "taginfo"
 #define        CVSROOTADM_EDITINFO     "editinfo"
 #define        CVSROOTADM_HISTORY      "history"
+#define CVSROOTADM_VALTAGS     "val-tags"
 #define        CVSROOTADM_IGNORE       "cvsignore"
 #define        CVSROOTADM_CHECKOUTLIST "checkoutlist"
-#define CVSROOTADM_WRAPPER      "cvswrappers"
+#define CVSROOTADM_WRAPPER     "cvswrappers"
+#define CVSROOTADM_NOTIFY      "notify"
+#define CVSROOTADM_USERS       "users"
+
 #define CVSNULLREPOS           "Emptydir"      /* an empty directory */
 
 /* support for the modules file (CVSROOTADM_MODULES) */
@@ -188,7 +216,6 @@ extern int errno;
 #define        CVSRFL          "#cvs.rfl"
 #define        CVSWFL          "#cvs.wfl"
 #define CVSRFLPAT      "#cvs.rfl.*"    /* wildcard expr to match read locks */
-#define        CVSEXT_OPT      ",p"
 #define        CVSEXT_LOG      ",t"
 #define        CVSPREFIX       ",,"
 #define CVSDOTIGNORE   ".cvsignore"
@@ -235,6 +262,9 @@ extern int errno;
 #define        IGNORE_ENV      "CVSIGNORE"     /* More files to ignore */
 #define WRAPPER_ENV     "CVSWRAPPERS"   /* name of the wrapper file */
 
+#define        CVSUMASK_ENV    "CVSUMASK"      /* Effective umask for repository */
+/* #define     CVSUMASK_DFLT              Set by config.h */
+
 /*
  * If the beginning of the Repository matches the following string, strip it
  * so that the output to the logfile does not contain a full pathname.
@@ -257,6 +287,7 @@ extern int errno;
 /* structure of a entry record */
 struct entnode
 {
+    char *user;
     char *version;
     char *timestamp;
     char *options;
@@ -304,23 +335,52 @@ typedef enum classify_type Ctype;
  */
 struct vers_ts
 {
-    char *vn_user;                     /* rcs version user file derives from
-                                        * it can have the following special
-                                        * values:
-                                        *    empty = no user file
-                                        *    0 = user file is new
-                                        *    -vers = user file to be removed */
-    char *vn_rcs;                      /* the version for the rcs file
-                                        * (tag version?)        */
-    char *ts_user;                     /* the timestamp for the user file */
-    char *ts_rcs;                      /* the user timestamp from entries */
-    char *options;                     /* opts from Entries file
-                                        * (keyword expansion)   */
-    char *ts_conflict;                 /* Holds time_stamp of conflict */
-    char *tag;                         /* tag stored in the Entries file */
-    char *date;                                /* date stored in the Entries file */
-    Entnode *entdata;                  /* pointer to entries file node  */
-    RCSNode *srcfile;                  /* pointer to parsed src file info */
+    /* rcs version user file derives from, from CVS/Entries.
+     * it can have the following special values:
+     *    empty = no user file
+     *    0 = user file is new
+     *    -vers = user file to be removed.  */
+    char *vn_user;
+
+    /* Numeric revision number corresponding to ->vn_tag (->vn_tag
+       will often be symbolic).  */
+    char *vn_rcs;
+    /* If ->tag corresponds to a tag which really exists in this file,
+       this is just a copy of ->tag.  If not, this is either NULL or
+       the head revision.  (Or something like that, see RCS_getversion
+       and friends).  */
+    char *vn_tag;
+
+    /* This is the timestamp from stating the file in the working directory.
+       It is NULL if there is no file in the working directory.  */
+    char *ts_user;
+    /* Timestamp from CVS/Entries.  For the server, ts_user and ts_rcs
+       are computed in a slightly different way, but the fact remains that
+       if they are equal the file in the working directory is unmodified
+       and if they differ it is modified.  */
+    char *ts_rcs;
+
+    /* Options from CVS/Entries (keyword expansion).  */
+    char *options;
+
+    /* If non-NULL, there was a conflict (or merely a merge?  See merge_file)
+       and the time stamp in this field is the time stamp of the working
+       directory file which was created with the conflict markers in it.
+       This is from CVS/Entries.  */
+    char *ts_conflict;
+
+    /* Tag specified on the command line, or if none, tag stored in
+       CVS/Entries.  */
+    char *tag;
+    /* Date specified on the command line, or if none, date stored in
+       CVS/Entries.  */
+    char *date;
+
+    /* Pointer to entries file node  */
+    Entnode *entdata;
+
+    /* Pointer to parsed src file info */
+    RCSNode *srcfile;
 };
 typedef struct vers_ts Vers_TS;
 
@@ -351,7 +411,7 @@ enum direnter_type
 };
 typedef enum direnter_type Dtype;
 
-extern char *program_name, *command_name;
+extern char *program_name, *program_path, *command_name;
 extern char *Rcsbin, *Editor, *CVSroot;
 #ifdef CVSADM_ROOT
 extern char *CVSADM_Root;
@@ -361,6 +421,7 @@ extern char *CurDir;
 extern int really_quiet, quiet;
 extern int use_editor;
 extern int cvswrite;
+extern mode_t cvsumask;
 
 extern int trace;              /* Show all commands */
 extern int noexec;             /* Don't modify disk anywhere */
@@ -398,11 +459,14 @@ char *time_stamp PROTO((char *file));
 char *xmalloc PROTO((size_t bytes));
 char *xrealloc PROTO((char *ptr, size_t bytes));
 char *xstrdup PROTO((const char *str));
+void strip_trailing_newlines PROTO((char *str));
 int No_Difference PROTO((char *file, Vers_TS * vers, List * entries,
                         char *repository, char *update_dir));
-int Parse_Info PROTO((char *infofile, char *repository, int PROTO((*callproc)) PROTO(()), int all));
+typedef        int (*CALLPROC) PROTO((char *repository, char *value));
+int Parse_Info PROTO((char *infofile, char *repository, CALLPROC callproc, int all));
 int Reader_Lock PROTO((char *xrepository));
-int SIG_register PROTO((int sig, RETSIGTYPE PROTO((*fn)) PROTO(())));
+typedef        RETSIGTYPE (*SIGCLEANUPPROC)    PROTO(());
+int SIG_register PROTO((int sig, SIGCLEANUPPROC sigcleanup));
 int Writer_Lock PROTO((List * list));
 int ign_name PROTO((char *name));
 int isdir PROTO((const char *file));
@@ -410,20 +474,32 @@ int isfile PROTO((const char *file));
 int islink PROTO((const char *file));
 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 *last_component PROTO((char *path));
 
-int joining PROTO((void));
 int numdots PROTO((const char *s));
 int unlink_file PROTO((const char *f));
+int link_file PROTO ((const char *from, const char *to));
 int unlink_file_dir PROTO((const char *f));
 int update PROTO((int argc, char *argv[]));
 int xcmp PROTO((const char *file1, const char *file2));
 int yesno PROTO((void));
+void *valloc PROTO((size_t bytes));
 time_t get_date PROTO((char *date, struct timeb *now));
 void Create_Admin PROTO((char *dir, char *update_dir,
                         char *repository, char *tag, char *date));
+\f
 void Lock_Cleanup PROTO((void));
+
+/* Writelock an entire subtree, well the part specified by ARGC, ARGV, LOCAL,
+   and AFLAG, anyway.  */
+void lock_tree_for_write PROTO ((int argc, char **argv, int local, int aflag));
+
+/* Remove locks set by lock_tree_for_write.  Currently removes readlocks
+   too.  */
+void lock_tree_cleanup PROTO ((void));
+\f
 void ParseTag PROTO((char **tagp, char **datep));
 void Scratch_Entry PROTO((List * list, char *fname));
 void WriteTag PROTO((char *dir, char *tag, char *date));
@@ -435,11 +511,18 @@ void (*error_set_cleanup PROTO((void (*) (void)))) PROTO ((void));
 void fperror PROTO((FILE * fp, int status, int errnum, char *message,...));
 void free_names PROTO((int *pargc, char *argv[]));
 void freevers_ts PROTO((Vers_TS ** versp));
+
 void ign_add PROTO((char *ign, int hold));
 void ign_add_file PROTO((char *file, int hold));
 void ign_setup PROTO((void));
 void ign_dir_add PROTO((char *name));
 int ignore_directory PROTO((char *name));
+typedef void (*Ignore_proc) PROTO ((char *, char *));
+extern void ignore_files PROTO ((List *, char *, Ignore_proc));
+extern int ign_inhibit_server;
+
+#include "update.h"
+
 void line2argv PROTO((int *pargc, char *argv[], char *line));
 void make_directories PROTO((const char *name));
 void make_directory PROTO((const char *name));
@@ -468,21 +551,27 @@ Vers_TS *Version_TS PROTO((char *repository, char *options, char *tag,
                     int set_time, List * entries, List * xfiles));
 void do_editor PROTO((char *dir, char **messagep,
                      char *repository, List * changes));
+
+typedef        int (*CALLBACKPROC)     PROTO((int *pargc, char *argv[], char *where,
+       char *mwhere, char *mfile, int horten, int local_specified,
+       char *omodule, char *msg));
+typedef        int (*FILEPROC)         PROTO((char *file, char *update_dir, char *repository,
+       List *  entries, List * srcfiles));
+typedef        int (*FILESDONEPROC)    PROTO((int err, char *repository, char *update_dir));
+typedef        Dtype (*DIRENTPROC)     PROTO((char *dir, char *repos, char *update_dir));
+typedef        int (*DIRLEAVEPROC)     PROTO((char *dir, int err, char *update_dir));
+
 int do_module PROTO((DBM * db, char *mname, enum mtype m_type, char *msg,
-              int PROTO((*callback_proc)) (), char *where, int shorten,
-              int local_specified, int run_module_prog, char *extra_arg));
-int do_recursion PROTO((int PROTO((*xfileproc)) (), int PROTO((*xfilesdoneproc)) (),
-                 Dtype PROTO((*xdirentproc)) (), int PROTO((*xdirleaveproc)) (),
+               CALLBACKPROC callback_proc, char *where, int shorten,
+               int local_specified, int run_module_prog, char *extra_arg));
+int do_recursion PROTO((FILEPROC xfileproc, FILESDONEPROC xfilesdoneproc,
+                 DIRENTPROC xdirentproc, DIRLEAVEPROC xdirleaveproc,
                  Dtype xflags, int xwhich, int xaflag, int xreadlock,
                  int xdosrcs));
-int do_update PROTO((int argc, char *argv[], char *xoptions, char *xtag,
-              char *xdate, int xforce, int local, int xbuild,
-              int xaflag, int xprune, int xpipeout, int which,
-              char *xjoin_rev1, char *xjoin_rev2, char *preload_update_dir));
 void history_write PROTO((int type, char *update_dir, char *revs, char *name,
                    char *repository));
-int start_recursion PROTO((int PROTO((*fileproc)) (), int PROTO((*filesdoneproc)) (),
-                    Dtype PROTO((*direntproc)) (), int PROTO((*dirleaveproc)) (),
+int start_recursion PROTO((FILEPROC fileproc, FILESDONEPROC filesdoneproc,
+                    DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc,
                     int argc, char *argv[], int local, int which,
                     int aflag, int readlock, char *update_preload,
                     int dosrcs, int wd_is_repos));
@@ -519,7 +608,7 @@ void close_on_exec PROTO((int));
 int filter_stream_through_program PROTO((int, int, char **, pid_t *));
 
 pid_t waitpid PROTO((pid_t, int *, int));
-
+\f
 /* Wrappers.  */
 
 typedef enum { WRAP_MERGE, WRAP_COPY } WrapMergeMethod;
@@ -530,5 +619,20 @@ int   wrap_name_has PROTO((const char *name,WrapMergeHas has));
 char *wrap_tocvs_process_file PROTO((const char *fileName));
 int   wrap_merge_is_copy PROTO((const char *fileName));
 char *wrap_fromcvs_process_file PROTO((const char *fileName));
+/* Pathname expansion */
+char *expand_path PROTO((char *name));
 void wrap_add_file PROTO((const char *file,int temp));
 void wrap_add PROTO((char *line,int temp));
+\f
+int watch PROTO ((int argc, char **argv));
+int edit PROTO ((int argc, char **argv));
+int unedit PROTO ((int argc, char **argv));
+int editors PROTO ((int argc, char **argv));
+int watchers PROTO ((int argc, char **argv));
+\f
+#if defined(AUTH_CLIENT_SUPPORT) || defined(AUTH_SERVER_SUPPORT)
+char *scramble PROTO ((char *str));
+char *descramble PROTO ((char *str));
+#endif /* AUTH_CLIENT_SUPPORT || AUTH_SERVER_SUPPORT */
+
+extern void tag_check_valid PROTO ((char *, int, char **, int, int, char *));
index 59216f3..ffdd351 100644 (file)
@@ -17,8 +17,6 @@ static const char rcsid[] = "$CVSid: @(#)lock.c 1.50 94/09/30 $";
 USE(rcsid);
 #endif
 
-extern char *ctime ();
-
 static int readers_exist PROTO((char *repository));
 static int set_lock PROTO((char *repository, int will_wait));
 static void clear_lock PROTO((void));
@@ -28,6 +26,7 @@ static int unlock_proc PROTO((Node * p, void *closure));
 static int write_lock PROTO((char *repository));
 static void unlock PROTO((char *repository));
 static void lock_wait PROTO((char *repository));
+static int Check_Owner PROTO((char *lockdir));
 
 static char lockers_name[20];
 static char *repository;
@@ -80,19 +79,18 @@ unlock (repository)
     char *repository;
 {
     char tmp[PATH_MAX];
-    struct stat sb;
 
     if (readlock[0] != '\0')
     {
        (void) sprintf (tmp, "%s/%s", repository, readlock);
-       if (unlink (tmp) < 0 && errno != ENOENT)
+       if (unlink (tmp) < 0 && ! existence_error (errno))
            error (0, errno, "failed to remove lock %s", tmp);
     }
 
     if (writelock[0] != '\0')
     {
        (void) sprintf (tmp, "%s/%s", repository, writelock);
-       if (unlink (tmp) < 0 && errno != ENOENT)
+       if (unlink (tmp) < 0 && ! existence_error (errno))
            error (0, errno, "failed to remove lock %s", tmp);
     }
 
@@ -103,15 +101,52 @@ unlock (repository)
      */
     if (writelock[0] != '\0' || (readlock[0] != '\0' && cleanup_lckdir)) 
     {
-       (void) sprintf (tmp, "%s/%s", repository, CVSLCK);
-       if (stat (tmp, &sb) != -1 && sb.st_uid == geteuid ())
-       {
+           (void) sprintf (tmp, "%s/%s", repository, CVSLCK);
+           if (Check_Owner(tmp))
+           {
+#ifdef AFSCVS
+               char rmuidlock[PATH_MAX];
+               sprintf(rmuidlock, "rm -f %s/uidlock%d", tmp, geteuid() );
+               system(rmuidlock);
+#endif
            (void) rmdir (tmp);
-       }
+           }
     }
     cleanup_lckdir = 0;
 }
 
+/*
+ * Check the owner of a lock.  Returns 1 if we own it, 0 otherwise.
+ */
+static int
+Check_Owner(lockdir)
+     char *lockdir;
+{
+  struct stat sb;
+
+#ifdef AFSCVS
+  /* In the Andrew File System (AFS), user ids from stat don't match
+     those from geteuid().  The AFSCVS code can deal with either AFS or
+     non-AFS repositories; the non-AFSCVS code is faster.  */
+  char uidlock[PATH_MAX];
+
+  /* Check if the uidlock is in the lock directory */
+  sprintf(uidlock, "%s/uidlock%d", lockdir, geteuid() );
+  if( stat(uidlock, &sb) != -1)
+    return 1;   /* The file exists, therefore we own the lock */
+  else
+    return 0;  /* The file didn't exist or some other error.
+                * Assume that we don't own it.
+                */
+#else
+  if (stat (lockdir, &sb) != -1 && sb.st_uid == geteuid ())
+    return 1;
+  else
+    return 0;
+#endif
+}  /* end Check_Owner() */
+
+
 /*
  * Create a lock file for readers
  */
@@ -159,7 +194,7 @@ Reader_Lock (xrepository)
        error (0, errno, "cannot create read lock in repository `%s'",
               xrepository);
        readlock[0] = '\0';
-       if (unlink (tmp) < 0 && errno != ENOENT)
+       if (unlink (tmp) < 0 && ! existence_error (errno))
            error (0, errno, "failed to remove lock %s", tmp);
        return (1);
     }
@@ -296,7 +331,7 @@ write_lock (repository)
     {
        error (0, errno, "cannot create write lock in repository `%s'",
               repository);
-       if (unlink (tmp) < 0 && errno != ENOENT)
+       if (unlink (tmp) < 0 && ! existence_error (errno))
            error (0, errno, "failed to remove lock %s", tmp);
        return (L_ERROR);
     }
@@ -327,7 +362,7 @@ write_lock (repository)
        {
            int xerrno = errno;
 
-           if (unlink (tmp) < 0 && errno != ENOENT)
+           if (unlink (tmp) < 0 && ! existence_error (errno))
                error (0, errno, "failed to remove lock %s", tmp);
 
            /* free the lock dir if we created it */
@@ -356,7 +391,7 @@ static int
 readers_exist (repository)
     char *repository;
 {
-    char line[MAXLINELEN];
+    char *line;
     DIR *dirp;
     struct dirent *dp;
     struct stat sb;
@@ -379,6 +414,7 @@ again:
            (void) time (&now);
 #endif
 
+           line = xmalloc (strlen (repository) + strlen (dp->d_name) + 5);
            (void) sprintf (line, "%s/%s", repository, dp->d_name);
            if (stat (line, &sb) != -1)
            {
@@ -391,11 +427,13 @@ again:
                if (now >= (sb.st_ctime + CVSLCKAGE) && unlink (line) != -1)
                {
                    (void) closedir (dirp);
+                   free (line);
                    goto again;
                }
 #endif
                set_lockers_name (&sb);
            }
+           free (line);
 
            ret = 1;
            break;
@@ -439,6 +477,7 @@ set_lock (repository, will_wait)
     int will_wait;
 {
     struct stat sb;
+    mode_t omask;
 #ifdef CVS_FUDGELOCKS
     time_t now;
 #endif
@@ -453,14 +492,39 @@ set_lock (repository, will_wait)
     cleanup_lckdir = 0;
     for (;;)
     {
+       int status = -1;
+       omask = umask (cvsumask);
        SIG_beginCrSect ();
        if (CVS_MKDIR (masterlock, 0777) == 0)
        {
+#ifdef AFSCVS
+           char uidlock[PATH_MAX];
+           FILE *fp;
+
+           sprintf(uidlock, "%s/uidlock%d", masterlock, geteuid() );
+           if ((fp = fopen(uidlock, "w+")) == NULL)
+           {
+               /* We failed to create the uidlock,
+                  so rm masterlock and leave */
+               rmdir(masterlock);
+               SIG_endCrSect ();
+               status = L_ERROR;
+               goto out;
+           }
+
+           /* We successfully created the uid lock, so close the file */
+           fclose(fp);
+#endif
            cleanup_lckdir = 1;
            SIG_endCrSect ();
-           return (L_OK);
+           status = L_OK;
+           goto out;
        }
        SIG_endCrSect ();
+      out:
+       (void) umask (omask);
+       if (status != -1)
+           return status;
 
        if (errno != EEXIST)
        {
@@ -476,7 +540,7 @@ set_lock (repository, will_wait)
         */
        if (stat (masterlock, &sb) < 0)
        {
-           if (errno == ENOENT)
+           if (existence_error (errno))
                continue;
 
            error (0, errno, "couldn't stat lock directory `%s'", masterlock);
@@ -492,6 +556,12 @@ set_lock (repository, will_wait)
        (void) time (&now);
        if (now >= (sb.st_ctime + CVSLCKAGE))
        {
+#ifdef AFSCVS
+         /* Remove the uidlock first */
+         char rmuidlock[PATH_MAX];
+         sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() );
+         system(rmuidlock);
+#endif
            if (rmdir (masterlock) >= 0)
                continue;
        }
@@ -514,6 +584,12 @@ set_lock (repository, will_wait)
 static void
 clear_lock()
 {
+#ifdef AFSCVS
+  /* Remove the uidlock first */
+  char rmuidlock[PATH_MAX];
+  sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() );
+  system(rmuidlock);
+#endif
     if (rmdir (masterlock) < 0)
        error (0, errno, "failed to remove lock dir `%s'", masterlock);
     cleanup_lckdir = 0;
@@ -533,3 +609,70 @@ lock_wait (repos)
           lockers_name, repos);
     (void) sleep (CVSLCKSLEEP);
 }
+\f
+static int lock_filesdoneproc PROTO ((int err, char *repository,
+                                     char *update_dir));
+static int fsortcmp PROTO((const Node * p, const Node * q));
+
+static List *lock_tree_list;
+
+/*
+ * Create a list of repositories to lock
+ */
+/* ARGSUSED */
+static int
+lock_filesdoneproc (err, repository, update_dir)
+    int err;
+    char *repository;
+    char *update_dir;
+{
+    Node *p;
+
+    p = getnode ();
+    p->type = LOCK;
+    p->key = xstrdup (repository);
+    /* FIXME-KRP: this error condition should not simply be passed by. */
+    if (p->key == NULL || addnode (lock_tree_list, p) != 0)
+       freenode (p);
+    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;
+    char **argv;
+    int local;
+    int aflag;
+{
+    int err;
+    /*
+     * Run the recursion processor to find all the dirs to lock and lock all
+     * the dirs
+     */
+    lock_tree_list = getlist ();
+    err = start_recursion ((FILEPROC) NULL, lock_filesdoneproc,
+                          (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, argc,
+                          argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0,
+                          0);
+    sortlist (lock_tree_list, fsortcmp);
+    if (Writer_Lock (lock_tree_list) != 0)
+       error (1, 0, "lock failed - giving up");
+}
+
+void
+lock_tree_cleanup ()
+{
+    Lock_Cleanup ();
+    dellist (&lock_tree_list);
+}
index 9cefce0..74613d7 100644 (file)
@@ -21,6 +21,7 @@
  *             commit          Checks files into the repository
  *             diff            Runs diffs between revisions
  *             log             Prints "rlog" information for files
+ *             login           Record user, host, repos, password
  *             add             Adds an entry to the repository
  *             remove          Removes an entry from the repository
  *             status          Status info on the revisions
@@ -33,7 +34,6 @@
  */
 
 #include "cvs.h"
-#include "patchlevel.h"
 
 #if HAVE_KERBEROS
 #include <sys/socket.h>
@@ -50,7 +50,12 @@ USE(rcsid);
 #endif
 
 char *program_name;
-char *command_name = "";
+char *program_path;
+/*
+ * Initialize comamnd_name to "cvs" so that the first call to
+ * read_cvsrc tries to find global cvs options.
+ */
+char *command_name = "cvs";
 
 /*
  * Since some systems don't define this...
@@ -61,6 +66,9 @@ char *command_name = "";
 
 char hostname[MAXHOSTNAMELEN];
 
+#ifdef AUTH_CLIENT_SUPPORT
+int use_authenticating_server = FALSE;
+#endif /* AUTH_CLIENT_SUPPORT */
 int use_editor = TRUE;
 int use_cvsrc = TRUE;
 int cvswrite = !CVSREAD_DFLT;
@@ -70,6 +78,7 @@ int trace = FALSE;
 int noexec = FALSE;
 int readonlyfs = FALSE;
 int logoff = FALSE;
+mode_t cvsumask = UMASK_DFLT;
 
 char *CurDir;
 
@@ -94,6 +103,9 @@ int diff PROTO((int argc, char **argv));
 int history PROTO((int argc, char **argv));
 int import PROTO((int argc, char **argv));
 int cvslog PROTO((int argc, char **argv));
+#ifdef AUTH_CLIENT_SUPPORT
+int login PROTO((int argc, char **argv));
+#endif /* AUTH_CLIENT_SUPPORT */
 int patch PROTO((int argc, char **argv));
 int release PROTO((int argc, char **argv));
 int cvsremove PROTO((int argc, char **argv));
@@ -121,24 +133,29 @@ const struct cmd
 #endif
 
     CMD_ENTRY("add",      "ad",    "new",     add,       client_add),
-#ifndef CVS_NOADMIN
     CMD_ENTRY("admin",    "adm",   "rcs",     admin,     client_admin),
-#endif
     CMD_ENTRY("checkout", "co",    "get",     checkout,  client_checkout),
     CMD_ENTRY("commit",   "ci",    "com",     commit,    client_commit),
     CMD_ENTRY("diff",     "di",    "dif",     diff,      client_diff),
+    CMD_ENTRY("edit",     "edit",  "edit",    edit,      client_edit),
+    CMD_ENTRY("editors",  "editors","editors",editors,   client_editors),
     CMD_ENTRY("export",   "exp",   "ex",      checkout,  client_export),
     CMD_ENTRY("history",  "hi",    "his",     history,   client_history),
     CMD_ENTRY("import",   "im",    "imp",     import,    client_import),
     CMD_ENTRY("log",      "lo",    "rlog",    cvslog,    client_log),
+#ifdef AUTH_CLIENT_SUPPORT
+    CMD_ENTRY("login",    "logon", "lgn",     login,     login),
+#endif /* AUTH_CLIENT_SUPPORT */
     CMD_ENTRY("rdiff",    "patch", "pa",      patch,     client_rdiff),
     CMD_ENTRY("release",  "re",    "rel",     release,   client_release),
     CMD_ENTRY("remove",   "rm",    "delete",  cvsremove, client_remove),
     CMD_ENTRY("status",   "st",    "stat",    status,    client_status),
     CMD_ENTRY("rtag",     "rt",    "rfreeze", rtag,      client_rtag),
     CMD_ENTRY("tag",      "ta",    "freeze",  tag,       client_tag),
+    CMD_ENTRY("unedit",   "unedit","unedit",  unedit,    client_unedit),
     CMD_ENTRY("update",   "up",    "upd",     update,    client_update),
-
+    CMD_ENTRY("watch",    "watch", "watch",   watch,     client_watch),
+    CMD_ENTRY("watchers", "watchers","watchers",watchers,client_watchers),
 #ifdef SERVER_SUPPORT
     /*
      * The client_func is also server because we might have picked up a
@@ -173,23 +190,38 @@ static const char *const usg[] =
     "        -z #         Use 'gzip -#' for net traffic if possible.\n",
 #endif
     "\n",
-    "    and where 'command' is:\n",
+    "    and where 'command' is: add, admin, etc. (use the --help-commands\n",
+    "    option for a list of commands)\n",
+    NULL,
+};
+
+static const char *const cmd_usage[] =
+{
+    "CVS commands are:\n",
     "        add          Adds a new file/directory to the repository\n",
     "        admin        Administration front end for rcs\n",
     "        checkout     Checkout sources for editing\n",
     "        commit       Checks files into the repository\n",
     "        diff         Runs diffs between revisions\n",
+    "        edit         Get ready to edit a watched file\n",
+    "        editors      See who is editing a watched file\n",
     "        history      Shows status of files and users\n",
     "        import       Import sources into CVS, using vendor branches\n",
     "        export       Export sources from CVS, similar to checkout\n",
     "        log          Prints out 'rlog' information for files\n",
+#ifdef AUTH_CLIENT_SUPPORT
+    "        login        Prompt for password for authenticating server.\n",
+#endif /* AUTH_CLIENT_SUPPORT */
     "        rdiff        'patch' format diffs between releases\n",
     "        release      Indicate that a Module is no longer in use\n",
     "        remove       Removes an entry from the repository\n",
     "        status       Status info on the revisions\n",
     "        tag          Add a symbolic tag to checked out version of RCS file\n",
+    "        unedit       Undo an edit command\n",
     "        rtag         Add a symbolic tag to the RCS file\n",
     "        update       Brings work tree in sync with repository\n",
+    "        watch        Set watches\n",
+    "        watchers     See who is watching a file\n",
     NULL,
 };
 
@@ -200,7 +232,7 @@ main_cleanup ()
 }
 
 static void
-error_cleanup ()
+error_cleanup PROTO((void))
 {
     Lock_Cleanup();
 #ifdef SERVER_SUPPORT
@@ -209,8 +241,6 @@ error_cleanup ()
 #endif
 }
 
-#define KF_GETOPT_LONG 1
-
 int
 main (argc, argv)
     int argc;
@@ -218,16 +248,18 @@ main (argc, argv)
 {
     extern char *version_string;
     extern char *config_string;
-    char *cp;
+    char *cp, *end;
     const struct cmd *cm;
     int c, err = 0;
-    static int help = FALSE, version_flag = FALSE;
+    static int help = FALSE;
+    static int version_flag = FALSE;
+    static int help_commands = FALSE;
     int rcsbin_update_env, cvs_update_env = 0;
-    char tmp[PATH_MAX];
     static struct option long_options[] =
       {
         {"help", 0, &help, TRUE},
         {"version", 0, &version_flag, TRUE},
+       {"help-commands", 0, &help_commands, TRUE},
         {0, 0, 0, 0}
       };
     /* `getopt_long' stores the option index here, but right now we
@@ -236,9 +268,15 @@ main (argc, argv)
 
     error_set_cleanup (error_cleanup);
 
+/* The socket subsystems on NT and OS2 must be initialized before use */
+#ifdef INITIALIZE_SOCKET_SUBSYSTEM
+        INITIALIZE_SOCKET_SUBSYSTEM();
+#endif /* INITIALIZE_SOCKET_SUBSYSTEM */
+
     /*
      * Just save the last component of the path for error messages
      */
+    program_path = xstrdup (argv[0]);
     program_name = last_component (argv[0]);
 
     CurDir = xmalloc (PATH_MAX);
@@ -273,6 +311,19 @@ main (argc, argv)
        cvswrite = FALSE;
     if (getenv (CVSREADONLYFS_ENV))
        readonlyfs = TRUE;
+    if ((cp = getenv (CVSUMASK_ENV)) != NULL)
+    {
+       /* FIXME: Should be accepting symbolic as well as numeric mask.  */
+       cvsumask = strtol (cp, &end, 8) & 0777;
+       if (*end != '\0')
+           error (1, errno, "invalid umask value in %s (%s)",
+               CVSUMASK_ENV, cp);
+    }
+
+    /*
+     * Scan cvsrc file for global options.
+     */
+    read_cvsrc(&argc, &argv);
 
     /* This has the effect of setting getopt's ordering to REQUIRE_ORDER,
        which is what we need to distinguish between global options and
@@ -341,16 +392,14 @@ main (argc, argv)
 #endif
            case '?':
            default:
-               usage (usg);
+                usage (usg);
        }
     }
 
     if (version_flag == TRUE)
-      {
+    {
         (void) fputs (version_string, stdout);
         (void) fputs (config_string, stdout);
-        (void) sprintf (tmp, "Patch Level: %d\n", PATCHLEVEL);
-        (void) fputs (tmp, stdout);
         (void) fputs ("\n", stdout);
         (void) fputs ("Copyright (c) 1993-1994 Brian Berliner\n", stdout);
         (void) fputs ("Copyright (c) 1993-1994 david d `zoo' zuhn\n", stdout);
@@ -360,7 +409,9 @@ main (argc, argv)
         (void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout);
         (void) fputs ("a copy of which can be found with the CVS distribution kit.\n", stdout);
         exit (0);
-      }
+    }
+    else if (help_commands)
+       usage (cmd_usage);
 
     argc -= optind;
     argv += optind;
@@ -452,6 +503,21 @@ error 0 %s: no such user\n", user);
     }
 #endif /* HAVE_KERBEROS */
 
+
+#if defined(AUTH_SERVER_SUPPORT) && defined(SERVER_SUPPORT)
+    if (strcmp (argv[0], "pserver") == 0)
+    {
+      /* Gets username and password from client, authenticates, then
+         switches to run as that user and sends an ACK back to the
+         client. */
+      authenticate_connection ();
+      
+      /* Pretend we were invoked as a plain server.  */
+      argv[0] = "server";
+    }
+#endif /* AUTH_SERVER_SUPPORT && SERVER_SUPPORT */
+
+
 #ifdef CVSADM_ROOT
     /*
      * See if we are able to find a 'better' value for CVSroot in the
@@ -474,9 +540,9 @@ error 0 %s: no such user\n", user);
         }
 #ifdef CLIENT_SUPPORT
         else if (!getenv ("CVS_IGNORE_REMOTE_ROOT"))
-#else
+#else /* ! CLIENT_SUPPORT */
         else
-#endif
+#endif /* CLIENT_SUPPORT */
         {
             /*
             * Now for the hard part, compare the two directories. If they
@@ -496,6 +562,20 @@ error 0 %s: no such user\n", user);
     }
 #endif /* CVSADM_ROOT */
 
+    /* CVSroot may need fixing up, if an access-method was specified,
+     * but not a user.  Later code assumes that if CVSroot contains an
+     * access-method, then it also has a user.  We print a warning and
+     * die if we can't guarantee that.
+     */
+    if (CVSroot
+        && *CVSroot
+        && (CVSroot[0] == ':')
+        && (strchr (CVSroot, '@') == NULL))
+      {
+        error (1, 0,
+               "must also give a username if specifying access method");
+      }
+
     /*
      * Specifying just the '-H' flag to the sub-command causes a Usage
      * message to be displayed.
@@ -521,7 +601,7 @@ error 0 %s: no such user\n", user);
                error (1, 0, "You don't have a %s environment variable",
                       CVSROOT_ENV);
            (void) sprintf (path, "%s/%s", CVSroot, CVSROOTADM);
-           if (access (path, R_OK | X_OK))
+           if (!isaccessible (path, R_OK | X_OK))
            {
                save_errno = errno;
 #ifdef CLIENT_SUPPORT
@@ -537,7 +617,7 @@ error 0 %s: no such user\n", user);
            }
            (void) strcat (path, "/");
            (void) strcat (path, CVSROOTADM_HISTORY);
-           if (readonlyfs == 0 && isfile (path) && access (path, R_OK | W_OK))
+           if (readonlyfs == 0 && isfile (path) && !isaccessible (path, R_OK | W_OK))
            {
                save_errno = errno;
                error (0, 0,
diff --git a/gnu/usr.bin/cvs/src/patchlevel.h b/gnu/usr.bin/cvs/src/patchlevel.h
deleted file mode 100644 (file)
index 50d3863..0000000
+++ /dev/null
@@ -1 +0,0 @@
-#define        PATCHLEVEL      2
index 6bef7d8..c87e2f5 100644 (file)
@@ -1,4 +1,8 @@
+#include <assert.h>
 #include "cvs.h"
+#include "watch.h"
+#include "edit.h"
+#include "fileattr.h"
 
 #ifdef SERVER_SUPPORT
 
@@ -39,7 +43,6 @@ int status PROTO((int argc, char **argv));
 int tag PROTO((int argc, char **argv));
 int update PROTO((int argc, char **argv));
 \f
-void server_cleanup PROTO((int sig));
 
 /*
  * This is where we stash stuff we are going to use.  Format string
@@ -225,6 +228,8 @@ supported_response (name)
        if (strcmp (rs->name, name) == 0)
            return rs->status == rs_supported;
     error (1, 0, "internal error: testing support for unknown response?");
+    /* NOTREACHED */
+    return 0;
 }
 
 static void
@@ -281,7 +286,7 @@ serve_root (arg)
     if (error_pending()) return;
     
     (void) sprintf (path, "%s/%s", arg, CVSROOTADM);
-    if (access (path, R_OK | X_OK))
+    if (!isaccessible (path, R_OK | X_OK))
     {
        save_errno = errno;
        pending_error_text = malloc (80 + strlen (path));
@@ -291,7 +296,7 @@ serve_root (arg)
     }
     (void) strcat (path, "/");
     (void) strcat (path, CVSROOTADM_HISTORY);
-    if (readonlyfs == 0 && isfile (path) && access (path, R_OK | W_OK))
+    if (readonlyfs == 0 && isfile (path) && !isaccessible (path, R_OK | W_OK))
     {
        save_errno = errno;
        pending_error_text = malloc (80 + strlen (path));
@@ -349,12 +354,13 @@ serve_max_dotdot (arg)
     server_temp_dir = p;
 }
 \f
+static char *dir_name;
+
 static void
 dirswitch (dir, repos)
     char *dir;
     char *repos;
 {
-    char *dirname;
     int status;
     FILE *f;
 
@@ -362,31 +368,34 @@ dirswitch (dir, repos)
 
     if (error_pending()) return;
 
-    dirname = malloc (strlen (server_temp_dir) + strlen (dir) + 40);
-    if (dirname == NULL)
+    if (dir_name != NULL)
+       free (dir_name);
+
+    dir_name = malloc (strlen (server_temp_dir) + strlen (dir) + 40);
+    if (dir_name == NULL)
     {
        pending_error = ENOMEM;
        return;
     }
     
-    strcpy (dirname, server_temp_dir);
-    strcat (dirname, "/");
-    strcat (dirname, dir);
+    strcpy (dir_name, server_temp_dir);
+    strcat (dir_name, "/");
+    strcat (dir_name, dir);
 
-    status = mkdir_p (dirname);        
+    status = mkdir_p (dir_name);       
     if (status != 0
        && status != EEXIST)
     {
        pending_error = status;
-       pending_error_text = malloc (80 + strlen(dirname));
-       sprintf(pending_error_text, "E cannot mkdir %s", dirname);
+       pending_error_text = malloc (80 + strlen(dir_name));
+       sprintf(pending_error_text, "E cannot mkdir %s", dir_name);
        return;
     }
-    if (chdir (dirname) < 0)
+    if (chdir (dir_name) < 0)
     {
        pending_error = errno;
-       pending_error_text = malloc (80 + strlen(dirname));
-       sprintf(pending_error_text, "E cannot change to %s", dirname);
+       pending_error_text = malloc (80 + strlen(dir_name));
+       sprintf(pending_error_text, "E cannot change to %s", dir_name);
        return;
     }
     /*
@@ -433,8 +442,7 @@ dirswitch (dir, repos)
        sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT);
        return;
     }
-    free (dirname);
-}    
+}
 
 static void
 serve_repository (arg)
@@ -938,6 +946,206 @@ server_write_entries ()
     }
 }
 \f
+struct notify_note {
+    /* Directory in which this notification happens.  malloc'd*/
+    char *dir;
+
+    /* malloc'd.  */
+    char *filename;
+
+    /* The following three all in one malloc'd block, pointed to by TYPE.
+       Each '\0' terminated.  */
+    /* "E" or "U".  */
+    char *type;
+    /* time+host+dir */
+    char *val;
+    char *watches;
+
+    struct notify_note *next;
+};
+
+static struct notify_note *notify_list;
+/* Used while building list, to point to the last node that already exists.  */
+static struct notify_note *last_node;
+
+static void serve_notify PROTO ((char *));
+
+static void
+serve_notify (arg)
+    char *arg;
+{
+    struct notify_note *new;
+    char *data;
+
+    if (error_pending ()) return;
+
+    new = (struct notify_note *) malloc (sizeof (struct notify_note));
+    if (new == NULL)
+    {
+       pending_error = ENOMEM;
+       return;
+    }
+    if (dir_name == NULL)
+       goto error;
+    new->dir = malloc (strlen (dir_name) + 1);
+    if (new->dir == NULL)
+    {
+       pending_error = ENOMEM;
+       return;
+    }
+    strcpy (new->dir, dir_name);
+    new->filename = malloc (strlen (arg) + 1);
+    if (new->filename == NULL)
+    {
+       pending_error = ENOMEM;
+       return;
+    }
+    strcpy (new->filename, arg);
+
+    data = read_line (stdin);
+    if (data == NULL)
+    {
+       pending_error_text = malloc (80 + strlen (arg));
+       if (pending_error_text)
+       {
+           if (feof (stdin))
+               sprintf (pending_error_text,
+                        "E end of file reading mode for %s", arg);
+           else
+           {
+               sprintf (pending_error_text,
+                        "E error reading mode for %s", arg);
+               pending_error = errno;
+           }
+       }
+       else
+           pending_error = ENOMEM;
+    }
+    else if (data == NO_MEM_ERROR)
+    {
+       pending_error = ENOMEM;
+    }
+    else
+    {
+       char *cp;
+
+       new->type = data;
+       if (data[1] != '\t')
+           goto error;
+       data[1] = '\0';
+       cp = data + 2;
+       new->val = cp;
+       cp = strchr (cp, '\t');
+       if (cp == NULL)
+           goto error;
+       *cp++ = '+';
+       cp = strchr (cp, '\t');
+       if (cp == NULL)
+           goto error;
+       *cp++ = '+';
+       cp = strchr (cp, '\t');
+       if (cp == NULL)
+           goto error;
+       *cp++ = '\0';
+       new->watches = cp;
+       /* If there is another tab, ignore everything after it,
+          for future expansion.  */
+       cp = strchr (cp, '\t');
+       if (cp != NULL)
+       {
+           *cp = '\0';
+       }
+
+       new->next = NULL;
+
+       if (last_node == NULL)
+       {
+           notify_list = new;
+       }
+       else
+           last_node->next = new;
+       last_node = new;
+    }
+    return;
+  error:
+    pending_error_text = malloc (40);
+    if (pending_error_text)
+       strcpy (pending_error_text,
+               "E Protocol error; misformed Notify request");
+    pending_error = 0;
+    return;
+}
+
+/* Process all the Notify requests that we have stored up.  Returns 0
+   if successful, if not prints error message (via error()) and
+   returns negative value.  */
+static int
+server_notify ()
+{
+    struct notify_note *p;
+    char *repos;
+    List *list;
+    Node *node;
+    int status;
+
+    while (notify_list != NULL)
+    {
+       if (chdir (notify_list->dir) < 0)
+       {
+           error (0, errno, "cannot change to %s", notify_list->dir);
+           return -1;
+       }
+       repos = Name_Repository (NULL, NULL);
+
+       /* Now writelock.  */
+       list = getlist ();
+       node = getnode ();
+       node->type = LOCK;
+       node->key = xstrdup (repos);
+       status = addnode (list, node);
+       assert (status == 0);
+       Writer_Lock (list);
+
+       fileattr_startdir (repos);
+
+       notify_do (*notify_list->type, notify_list->filename, getcaller(),
+                  notify_list->val, notify_list->watches, repos);
+
+       printf ("Notified ");
+       if (use_dir_and_repos)
+       {
+           char *dir = notify_list->dir + strlen (server_temp_dir) + 1;
+           if (dir[0] == '\0')
+               fputs (".", stdout);
+           else
+               fputs (dir, stdout);
+           fputs ("/\n", stdout);
+       }
+       fputs (repos, stdout);
+       fputs ("/", stdout);
+       fputs (notify_list->filename, stdout);
+       fputs ("\n", stdout);
+
+       p = notify_list->next;
+       free (notify_list->filename);
+       free (notify_list->dir);
+       free (notify_list->type);
+       free (notify_list);
+       notify_list = p;
+
+       fileattr_write ();
+       fileattr_free ();
+
+       /* Remove the writelock.  */
+       Lock_Cleanup ();
+       dellist (&list);
+    }
+    /* do_cvs_command writes to stdout via write(), not stdio, so better
+       flush out the buffer.  */
+    fflush (stdout);
+    return 0;
+}
+\f
 static int argument_count;
 static char **argument_vector;
 static int argument_vector_size;
@@ -1084,6 +1292,17 @@ struct buffer_data
 /* The size we allocate for each buffer_data structure.  */
 #define BUFFER_DATA_SIZE (4096)
 
+#ifdef SERVER_FLOWCONTROL
+/* The maximum we'll queue to the remote client before blocking.  */
+# ifndef SERVER_HI_WATER
+#  define SERVER_HI_WATER (2 * 1024 * 1024)
+# endif /* SERVER_HI_WATER */
+/* When the buffer drops to this, we restart the child */
+# ifndef SERVER_LO_WATER
+#  define SERVER_LO_WATER (1 * 1024 * 1024)
+# endif /* SERVER_LO_WATER */
+#endif /* SERVER_FLOWCONTROL */
+
 /* Linked list of available buffer_data structures.  */
 static struct buffer_data *free_buffer_data;
 
@@ -1094,7 +1313,6 @@ static void buf_output PROTO((struct buffer *, const char *, int));
 static void buf_output0 PROTO((struct buffer *, const char *));
 static inline void buf_append_char PROTO((struct buffer *, int));
 static int buf_send_output PROTO((struct buffer *));
-static int buf_len PROTO((struct buffer *));
 static int set_nonblock PROTO((struct buffer *));
 static int set_block PROTO((struct buffer *));
 static int buf_send_counted PROTO((struct buffer *));
@@ -1107,6 +1325,11 @@ static int buf_input_data PROTO((struct buffer *, int *));
 static void buf_copy_lines PROTO((struct buffer *, struct buffer *, int));
 static int buf_copy_counted PROTO((struct buffer *, struct buffer *));
 
+#ifdef SERVER_FLOWCONTROL
+static int buf_count_mem PROTO((struct buffer *));
+static int set_nonblock_fd PROTO((int));
+#endif /* SERVER_FLOWCONTROL */
+
 /* Allocate more buffer_data structures.  */
 
 static void
@@ -1165,6 +1388,26 @@ buf_empty_p (buf)
     return 1;
 }
 
+#ifdef SERVER_FLOWCONTROL
+/*
+ * Count how much data is stored in the buffer..
+ * Note that each buffer is a malloc'ed chunk BUFFER_DATA_SIZE.
+ */
+
+static int
+buf_count_mem (buf)
+    struct buffer *buf;
+{
+    struct buffer_data *data;
+    int mem = 0;
+
+    for (data = buf->data; data != NULL; data = data->next)
+       mem += BUFFER_DATA_SIZE;
+
+    return mem;
+}
+#endif /* SERVER_FLOWCONTROL */
+
 /* Add data DATA of length LEN to BUF.  */
 
 static void
@@ -1256,29 +1499,6 @@ buf_append_char (buf, ch)
     }
 }
 
-/*
- * Count how many bytes are in the buffer
- */
-
-static int
-buf_len (buf)
-     struct buffer *buf;
-{
-    struct buffer_data *data;
-    int count = 0;
-
-    if (! buf->output)
-       abort ();
-
-    for (data = buf->data; data; data = data->next)
-    {
-       count += data->size;
-    }
-       
-    return (count);
-}
-
-
 /*
  * Send all the output we've been saving up.  Returns 0 for success or
  * errno code.  If the buffer has been set to be nonblocking, this
@@ -1351,11 +1571,27 @@ buf_send_output (buf)
     return 0;
 }
 
+#ifdef SERVER_FLOWCONTROL
 /*
  * Set buffer BUF to non-blocking I/O.  Returns 0 for success or errno
  * code.
  */
 
+static int
+set_nonblock_fd (fd)
+     int fd;
+{
+    int flags;
+
+    flags = fcntl (fd, F_GETFL, 0);
+    if (flags < 0)
+       return errno;
+    if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0)
+       return errno;
+    return 0;
+}
+#endif /* SERVER_FLOWCONTROL */
+
 static int
 set_nonblock (buf)
      struct buffer *buf;
@@ -1896,6 +2132,49 @@ buf_copy_counted (outbuf, inbuf)
     /*NOTREACHED*/
 }
 \f
+/* While processing requests, this buffer accumulates data to be sent to
+   the client, and then once we are in do_cvs_command, we use it
+   for all the data to be sent.  */
+static struct buffer buf_to_net;
+
+static void serve_questionable PROTO((char *));
+
+static void
+serve_questionable (arg)
+    char *arg;
+{
+    static int initted;
+
+    if (!initted)
+    {
+       /* Pick up ignores from CVSROOTADM_IGNORE, $HOME/.cvsignore on server,
+          and CVSIGNORE on server.  */
+       ign_setup ();
+       initted = 1;
+    }
+
+    if (dir_name == NULL)
+    {
+       buf_output0 (&buf_to_net, "E Protocol error: 'Directory' missing");
+       return;
+    }
+
+    if (!ign_name (arg))
+    {
+       char *update_dir;
+
+       buf_output (&buf_to_net, "M ? ", 4);
+       update_dir = dir_name + strlen (server_temp_dir) + 1;
+       if (!(update_dir[0] == '.' && update_dir[1] == '\0'))
+       {
+           buf_output0 (&buf_to_net, update_dir);
+           buf_output (&buf_to_net, "/", 1);
+       }
+       buf_output0 (&buf_to_net, arg);
+       buf_output (&buf_to_net, "\n", 1);
+    }
+}
+\f
 static struct buffer protocol;
 
 static void
@@ -1944,6 +2223,10 @@ input_memory_error (buf)
 static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain;
 static int max_command_fd;
 
+#ifdef SERVER_FLOWCONTROL
+static int flowcontrol_pipe[2];
+#endif /* SERVER_FLOWCONTROL */
+
 static void
 do_cvs_command (command)
     int (*command) PROTO((int argc, char **argv));
@@ -1981,6 +2264,8 @@ do_cvs_command (command)
     if (print_pending_error ())
        goto free_args_and_return;
 
+    (void) server_notify ();
+
     /*
      * We use a child process which actually does the operation.  This
      * is so we can intercept its standard output.  Even if all of CVS
@@ -2004,6 +2289,15 @@ do_cvs_command (command)
        print_error (errno);
        goto error_exit;
     }
+#ifdef SERVER_FLOWCONTROL
+    if (pipe (flowcontrol_pipe) < 0)
+    {
+       print_error (errno);
+       goto error_exit;
+    }
+    set_nonblock_fd (flowcontrol_pipe[0]);
+    set_nonblock_fd (flowcontrol_pipe[1]);
+#endif /* SERVER_FLOWCONTROL */
 
     dev_null_fd = open ("/dev/null", O_RDONLY);
     if (dev_null_fd < 0)
@@ -2043,6 +2337,9 @@ do_cvs_command (command)
        close (stdout_pipe[0]);
        close (stderr_pipe[0]);
        close (protocol_pipe[0]);
+#ifdef SERVER_FLOWCONTROL
+       close (flowcontrol_pipe[1]);
+#endif /* SERVER_FLOWCONTROL */
 
        /*
         * Set this in .bashrc if you want to give yourself time to attach
@@ -2065,13 +2362,15 @@ do_cvs_command (command)
 
     /* OK, sit around getting all the input from the child.  */
     {
-       struct buffer outbuf;
        struct buffer stdoutbuf;
        struct buffer stderrbuf;
        struct buffer protocol_inbuf;
        /* Number of file descriptors to check in select ().  */
        int num_to_check;
        int count_needed = 0;
+#ifdef SERVER_FLOWCONTROL
+       int have_flowcontrolled = 0;
+#endif /* SERVER_FLOWCONTROL */
 
        FD_ZERO (&command_fds_to_drain.fds);
        num_to_check = stdout_pipe[0];
@@ -2096,12 +2395,6 @@ do_cvs_command (command)
            goto error_exit;
        }
 
-       outbuf.data = outbuf.last = NULL;
-       outbuf.fd = STDOUT_FILENO;
-       outbuf.output = 1;
-       outbuf.nonblocking = 0;
-       outbuf.memory_error = outbuf_memory_error;
-
        stdoutbuf.data = stdoutbuf.last = NULL;
        stdoutbuf.fd = stdout_pipe[0];
        stdoutbuf.output = 0;
@@ -2120,7 +2413,7 @@ do_cvs_command (command)
        protocol_inbuf.nonblocking = 0;
        protocol_inbuf.memory_error = input_memory_error;
 
-       set_nonblock (&outbuf);
+       set_nonblock (&buf_to_net);
        set_nonblock (&stdoutbuf);
        set_nonblock (&stderrbuf);
        set_nonblock (&protocol_inbuf);
@@ -2146,6 +2439,15 @@ do_cvs_command (command)
        }
        protocol_pipe[1] = -1;
 
+#ifdef SERVER_FLOWCONTROL
+       if (close (flowcontrol_pipe[0]) < 0)
+       {
+           print_error (errno);
+           goto error_exit;
+       }
+       flowcontrol_pipe[0] = -1;
+#endif /* SERVER_FLOWCONTROL */
+
        if (close (dev_null_fd) < 0)
        {
            print_error (errno);
@@ -2160,24 +2462,42 @@ do_cvs_command (command)
            fd_set readfds;
            fd_set writefds;
            int numfds;
+#ifdef SERVER_FLOWCONTROL
+           int bufmemsize;
+
+           /*
+            * See if we are swamping the remote client and filling our VM.
+            * Tell child to hold off if we do.
+            */
+           bufmemsize = buf_count_mem (&buf_to_net);
+           if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER))
+           {
+               if (write(flowcontrol_pipe[1], "S", 1) == 1)
+                   have_flowcontrolled = 1;
+           }
+           else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER))
+           {
+               if (write(flowcontrol_pipe[1], "G", 1) == 1)
+                   have_flowcontrolled = 0;
+           }
+#endif /* SERVER_FLOWCONTROL */
 
            FD_ZERO (&readfds);
            FD_ZERO (&writefds);
-           if (! buf_empty_p (&outbuf))
-             FD_SET (STDOUT_FILENO, &writefds);
-           if ( buf_len (&outbuf) < 2*1024*1024) {
-               if (stdout_pipe[0] >= 0)
-               {
-                   FD_SET (stdout_pipe[0], &readfds);
-               }
-               if (stderr_pipe[0] >= 0)
-               {
-                   FD_SET (stderr_pipe[0], &readfds);
-               }
-               if (protocol_pipe[0] >= 0)
-               {
-                   FD_SET (protocol_pipe[0], &readfds);
-               }
+           if (! buf_empty_p (&buf_to_net))
+               FD_SET (STDOUT_FILENO, &writefds);
+
+           if (stdout_pipe[0] >= 0)
+           {
+               FD_SET (stdout_pipe[0], &readfds);
+           }
+           if (stderr_pipe[0] >= 0)
+           {
+               FD_SET (stderr_pipe[0], &readfds);
+           }
+           if (protocol_pipe[0] >= 0)
+           {
+               FD_SET (protocol_pipe[0], &readfds);
            }
 
            do {
@@ -2197,7 +2517,7 @@ do_cvs_command (command)
            if (FD_ISSET (STDOUT_FILENO, &writefds))
            {
                /* What should we do with errors?  syslog() them?  */
-               buf_send_output (&outbuf);
+               buf_send_output (&buf_to_net);
            }
 
            if (stdout_pipe[0] >= 0
@@ -2207,7 +2527,7 @@ do_cvs_command (command)
 
                status = buf_input_data (&stdoutbuf, (int *) NULL);
 
-               buf_copy_lines (&outbuf, &stdoutbuf, 'M');
+               buf_copy_lines (&buf_to_net, &stdoutbuf, 'M');
 
                if (status == -1)
                    stdout_pipe[0] = -1;
@@ -2218,7 +2538,7 @@ do_cvs_command (command)
                }
 
                /* What should we do with errors?  syslog() them?  */
-               buf_send_output (&outbuf);
+               buf_send_output (&buf_to_net);
            }
 
            if (stderr_pipe[0] >= 0
@@ -2228,7 +2548,7 @@ do_cvs_command (command)
 
                status = buf_input_data (&stderrbuf, (int *) NULL);
 
-               buf_copy_lines (&outbuf, &stderrbuf, 'E');
+               buf_copy_lines (&buf_to_net, &stderrbuf, 'E');
 
                if (status == -1)
                    stderr_pipe[0] = -1;
@@ -2239,7 +2559,7 @@ do_cvs_command (command)
                }
 
                /* What should we do with errors?  syslog() them?  */
-               buf_send_output (&outbuf);
+               buf_send_output (&buf_to_net);
            }
 
            if (protocol_pipe[0] >= 0
@@ -2258,7 +2578,8 @@ do_cvs_command (command)
                 */
                count_needed -= count_read;
                if (count_needed <= 0)
-                 count_needed = buf_copy_counted (&outbuf, &protocol_inbuf);
+                   count_needed = buf_copy_counted (&buf_to_net,
+                                                    &protocol_inbuf);
 
                if (status == -1)
                    protocol_pipe[0] = -1;
@@ -2269,7 +2590,7 @@ do_cvs_command (command)
                }
 
                /* What should we do with errors?  syslog() them?  */
-               buf_send_output (&outbuf);
+               buf_send_output (&buf_to_net);
            }
        }
 
@@ -2281,15 +2602,15 @@ do_cvs_command (command)
        if (! buf_empty_p (&stdoutbuf))
        {
            buf_append_char (&stdoutbuf, '\n');
-           buf_copy_lines (&outbuf, &stdoutbuf, 'M');
+           buf_copy_lines (&buf_to_net, &stdoutbuf, 'M');
        }
        if (! buf_empty_p (&stderrbuf))
        {
            buf_append_char (&stderrbuf, '\n');
-           buf_copy_lines (&outbuf, &stderrbuf, 'E');
+           buf_copy_lines (&buf_to_net, &stderrbuf, 'E');
        }
        if (! buf_empty_p (&protocol_inbuf))
-           buf_output0 (&outbuf,
+           buf_output0 (&buf_to_net,
                         "E Protocol error: uncounted data discarded\n");
 
        errs = 0;
@@ -2340,8 +2661,8 @@ E CVS locks may need cleaning up.\n",
         * OK, we've waited for the child.  By now all CVS locks are free
         * and it's OK to block on the network.
         */
-       set_block (&outbuf);
-       buf_send_output (&outbuf);
+       set_block (&buf_to_net);
+       buf_send_output (&buf_to_net);
     }
 
     if (errs)
@@ -2388,6 +2709,61 @@ E CVS locks may need cleaning up.\n",
     return;
 }
 \f
+#ifdef SERVER_FLOWCONTROL
+/*
+ * Called by the child at convenient points in the server's execution for
+ * the server child to block.. ie: when it has no locks active.
+ */
+void
+server_pause_check()
+{
+    int paused = 0;
+    char buf[1];
+
+    while (read (flowcontrol_pipe[0], buf, 1) == 1)
+    {
+       if (*buf == 'S')        /* Stop */
+           paused = 1;
+       else if (*buf == 'G')   /* Go */
+           paused = 0;
+       else
+           return;             /* ??? */
+    }
+    while (paused) {
+       int numfds, numtocheck;
+       fd_set fds;
+
+       FD_ZERO (&fds);
+       FD_SET (flowcontrol_pipe[0], &fds);
+       numtocheck = flowcontrol_pipe[0] + 1;
+       
+       do {
+           numfds = select (numtocheck, &fds, (fd_set *)0,
+                            (fd_set *)0, (struct timeval *)NULL);
+           if (numfds < 0
+               && errno != EINTR)
+           {
+               print_error (errno);
+               return;
+           }
+       } while (numfds < 0);
+           
+       if (FD_ISSET (flowcontrol_pipe[0], &fds))
+       {
+           while (read (flowcontrol_pipe[0], buf, 1) == 1)
+           {
+               if (*buf == 'S')        /* Stop */
+                   paused = 1;
+               else if (*buf == 'G')   /* Go */
+                   paused = 0;
+               else
+                   return;             /* ??? */
+           }
+       }
+    }
+}
+#endif /* SERVER_FLOWCONTROL */
+\f
 static void output_dir PROTO((char *, char *));
 
 static void
@@ -2437,18 +2813,18 @@ server_register (name, version, timestamp, options, tag, date, conflict)
 {
     int len;
 
+    if (options == NULL)
+       options = "";
+
     if (trace)
     {
        (void) fprintf (stderr,
                        "%c-> server_register(%s, %s, %s, %s, %s, %s, %s)\n",
                        (server_active) ? 'S' : ' ', /* silly */
-                       name, version, timestamp, options, tag,
-                       date, conflict);
+                       name, version, timestamp, options, tag ? tag : "",
+                       date ? date : "", conflict ? conflict : "");
     }
 
-    if (options == NULL)
-       options = "";
-
     if (entries_line != NULL)
     {
        /*
@@ -2555,6 +2931,41 @@ serve_ci (arg)
     do_cvs_command (commit);
 }
 
+static void
+checked_in_response (file, update_dir, repository)
+    char *file;
+    char *update_dir;
+    char *repository;
+{
+    if (supported_response ("Mode"))
+    {
+       struct stat sb;
+       char *mode_string;
+
+       if (stat (file, &sb) < 0)
+       {
+           /* Not clear to me why the file would fail to exist, but it
+              was happening somewhere in the testsuite.  */
+           if (!existence_error (errno))
+               error (0, errno, "cannot stat %s", file);
+       }
+       else
+       {
+           buf_output0 (&protocol, "Mode ");
+           mode_string = mode_to_string (sb.st_mode);
+           buf_output0 (&protocol, mode_string);
+           buf_output0 (&protocol, "\n");
+           free (mode_string);
+       }
+    }
+
+    buf_output0 (&protocol, "Checked-in ");
+    output_dir (update_dir, repository);
+    buf_output0 (&protocol, file);
+    buf_output (&protocol, "\n", 1);
+    new_entries_line ();
+}
+
 void
 server_checked_in (file, update_dir, repository)
     char *file;
@@ -2578,11 +2989,7 @@ server_checked_in (file, update_dir, repository)
     }
     else
     {
-       buf_output0 (&protocol, "Checked-in ");
-       output_dir (update_dir, repository);
-       buf_output0 (&protocol, file);
-       buf_output (&protocol, "\n", 1);
-       new_entries_line ();
+       checked_in_response (file, update_dir, repository);
     }
     buf_send_counted (&protocol);
 }
@@ -2597,18 +3004,18 @@ server_update_entries (file, update_dir, repository, updated)
     if (noexec)
        return;
     if (updated == SERVER_UPDATED)
-       buf_output0 (&protocol, "Checked-in ");
+       checked_in_response (file, update_dir, repository);
     else
     {
        if (!supported_response ("New-entry"))
            return;
        buf_output0 (&protocol, "New-entry ");
+       output_dir (update_dir, repository);
+       buf_output0 (&protocol, file);
+       buf_output (&protocol, "\n", 1);
+       new_entries_line ();
     }
 
-    output_dir (update_dir, repository);
-    buf_output0 (&protocol, file);
-    buf_output (&protocol, "\n", 1);
-    new_entries_line ();
     buf_send_counted (&protocol);
 }
 \f
@@ -2703,6 +3110,79 @@ serve_release (arg)
     do_cvs_command (release);
 }
 
+static void serve_watch_on PROTO ((char *));
+
+static void
+serve_watch_on (arg)
+    char *arg;
+{
+    do_cvs_command (watch_on);
+}
+
+static void serve_watch_off PROTO ((char *));
+
+static void
+serve_watch_off (arg)
+    char *arg;
+{
+    do_cvs_command (watch_off);
+}
+
+static void serve_watch_add PROTO ((char *));
+
+static void
+serve_watch_add (arg)
+    char *arg;
+{
+    do_cvs_command (watch_add);
+}
+
+static void serve_watch_remove PROTO ((char *));
+
+static void
+serve_watch_remove (arg)
+    char *arg;
+{
+    do_cvs_command (watch_remove);
+}
+
+static void serve_watchers PROTO ((char *));
+
+static void
+serve_watchers (arg)
+    char *arg;
+{
+    do_cvs_command (watchers);
+}
+
+static void serve_editors PROTO ((char *));
+
+static void
+serve_editors (arg)
+    char *arg;
+{
+    do_cvs_command (editors);
+}
+
+static int noop PROTO ((int, char **));
+
+static int
+noop (argc, argv)
+    int argc;
+    char **argv;
+{
+    return 0;
+}
+
+static void serve_noop PROTO ((char *));
+
+static void
+serve_noop (arg)
+    char *arg;
+{
+    do_cvs_command (noop);
+}
+\f
 static void
 serve_co (arg)
     char *arg;
@@ -2804,7 +3284,7 @@ server_updated (file, update_dir, repository, updated, file_info, checksum)
 
        if (stat (file, &sb) < 0)
        {
-           if (errno == ENOENT)
+           if (existence_error (errno))
            {
                /*
                 * If we have a sticky tag for a branch on which the
@@ -3102,7 +3582,14 @@ expand_proc (pargc, argv, where, mwhere, mfile, shorten,
        expansion. */
 
     if (mwhere != NULL)
-      printf ("Module-expansion %s\n", mwhere);
+    {
+       printf ("Module-expansion %s", mwhere);
+       if (mfile != NULL)
+       {
+           printf ("/%s", mfile);
+       }
+       printf ("\n");
+    }
     else
       {
        /* We may not need to do this anymore -- check the definition
@@ -3272,6 +3759,8 @@ struct request requests[] =
   REQ_LINE("Lost", serve_lost, rq_optional),
   REQ_LINE("UseUnchanged", serve_enable_unchanged, rq_enableme),
   REQ_LINE("Unchanged", serve_unchanged, rq_optional),
+  REQ_LINE("Notify", serve_notify, rq_optional),
+  REQ_LINE("Questionable", serve_questionable, rq_optional),
   REQ_LINE("Argument", serve_argument, rq_essential),
   REQ_LINE("Argumentx", serve_argumentx, rq_essential),
   REQ_LINE("Global_option", serve_global_option, rq_optional),
@@ -3294,6 +3783,13 @@ struct request requests[] =
   REQ_LINE("export", serve_export, rq_optional),
   REQ_LINE("history", serve_history, rq_optional),
   REQ_LINE("release", serve_release, rq_optional),
+  REQ_LINE("watch-on", serve_watch_on, rq_optional),
+  REQ_LINE("watch-off", serve_watch_off, rq_optional),
+  REQ_LINE("watch-add", serve_watch_add, rq_optional),
+  REQ_LINE("watch-remove", serve_watch_remove, rq_optional),
+  REQ_LINE("watchers", serve_watchers, rq_optional),
+  REQ_LINE("editors", serve_editors, rq_optional),
+  REQ_LINE("noop", serve_noop, rq_optional),
   REQ_LINE(NULL, NULL, rq_optional)
 
 #undef REQ_LINE
@@ -3316,6 +3812,7 @@ serve_valid_requests (arg)
     printf ("\nok\n");
 }
 
+#ifdef sun
 /*
  * Delete temporary files.  SIG is the signal making this happen, or
  * 0 if not called as a result of a signal.
@@ -3329,6 +3826,7 @@ static void wait_sig (sig)
   if (r == command_pid)
     command_pid_is_dead++;
 }
+#endif
 
 void
 server_cleanup (sig)
@@ -3556,7 +4054,14 @@ error ENOMEM Virtual memory exhausted.\n");
     argument_count = 1;
     argument_vector[0] = "Dummy argument 0";
 
+    buf_to_net.data = buf_to_net.last = NULL;
+    buf_to_net.fd = STDOUT_FILENO;
+    buf_to_net.output = 1;
+    buf_to_net.nonblocking = 0;
+    buf_to_net.memory_error = outbuf_memory_error;
+
     server_active = 1;
+
     while (1)
     {
        char *cmd, *orig_cmd;
@@ -3600,4 +4105,311 @@ error ENOMEM Virtual memory exhausted.\n");
     return 0;
 }
 
+\f
+#ifdef AUTH_SERVER_SUPPORT
+
+/* This was test code, which we may need again. */
+#if 0
+  /* If we were invoked this way, then stdin comes from the
+     client and stdout/stderr writes to it. */
+  int c;
+  while ((c = getc (stdin)) != EOF && c != '*')
+    {
+      printf ("%c", toupper (c));
+      fflush (stdout);
+    }
+  exit (0);
+#endif /* 1/0 */
+
+\f
+/* 
+ * 0 means no entry found for this user.
+ * 1 means entry found and password matches.
+ * 2 means entry found, but password does not match.
+ */
+int
+check_repository_password (username, password, repository)
+     char *username, *password, *repository;
+{
+    int retval = 0;
+    FILE *fp;
+    char *filename;
+    char *linebuf;
+    int ch;
+    int found_it = 0, namelen, linelen;
+
+    filename = xmalloc (strlen (repository)
+                       + 1
+                       + strlen ("CVSROOT")
+                       + 1
+                       + strlen ("passwd")
+                       + 1);
+
+    strcpy (filename, repository);
+    strcat (filename, "/CVSROOT");
+    strcat (filename, "/passwd");
+  
+    /* 32 is enough to cover the hashed password.  I don't know if this
+     * counts as an arbitrary limit or not; it really depends on how
+     * standardized crypt() is.
+     * Answer: FreeBSD and Debian have played with the idea of making
+     * crypt() do MD5 which has a longer value; it would better not to
+     * make assumptions.  So yes, FIXME: arbitrary limit.
+     */
+
+    /*            USERNAME        :   PASSWD   \n      \0     */
+    linelen = strlen (username) + 1  +  32  +   1   +   1;
+    linebuf = xmalloc (linelen);
+    memset (linebuf, 0, linelen);
+
+    fp = fopen (filename, "r");
+    if (fp == NULL)
+    {
+       if (!existence_error (errno))
+           error (0, errno, "cannot open %s", filename);
+       return 0;
+    }
+
+    /* Look for a relevant line -- one with this user's name. */
+    namelen = strlen (username);
+    while (fgets (linebuf, linelen, fp))
+    {
+       if ((strncmp (linebuf, username, namelen) == 0)
+           && (linebuf[namelen] == ':'))
+        {
+           found_it = 1;
+           break;
+        }
+       else if (! strchr (linebuf, '\n'))
+       {
+           while ((ch = getc (fp)) != '\n')
+               if (ch == EOF)
+                   break;
+       }
+    }
+    if (ferror (fp))
+       error (0, errno, "cannot read %s", filename);
+    if (fclose (fp) < 0)
+       error (0, errno, "cannot close %s", filename);
+
+    /* If found_it != 0, then linebuf contains the information we need. */
+    if (found_it)
+    {
+       char *found_password;
+
+       strtok (linebuf, ":");
+       found_password = strtok (NULL, ": \n");
+
+       if (strcmp (found_password, crypt (password, found_password)) == 0)
+           retval = 1;
+       else
+           retval = 2;
+    }
+    else
+       retval = 0;
+
+    free (filename);
+
+    return retval;
+}
+
+
+/* Return 1 if password matches, else 0. */
+int
+check_password (username, password, repository)
+     char *username, *password, *repository;
+{
+  int rc;
+
+  /* First we see if this user has a password in the CVS-specific
+     password file.  If so, that's enough to authenticate with.  If
+     not, we'll check /etc/passwd. */
+
+  rc = check_repository_password (username, password, repository);
+
+  if (rc == 1)
+    return 1;
+  else if (rc == 2)
+    return 0;
+  else if (rc == 0)
+    {
+      /* No cvs password found, so try /etc/passwd. */
+
+      struct passwd *pw;
+      char *found_passwd;
+
+      pw = getpwnam (username);
+      if (pw == NULL)
+        {
+          printf ("E Fatal error, aborting.\n"
+                  "error 0 %s: no such user\n", username);
+          exit (1);
+        }
+      found_passwd = pw->pw_passwd;
+      
+      if (found_passwd && *found_passwd)
+        return (! strcmp (found_passwd, crypt (password, found_passwd)));
+      else if (password && *password)
+        return 1;
+      else
+        return 0;
+    }
+  else
+    {
+      /* Something strange happened.  We don't know what it was, but
+         we certainly won't grant authorization. */
+      return 0;
+    }
+}
+
+
+/* Read username and password from client (i.e., stdin).
+   If correct, then switch to run as that user and send an ACK to the
+   client via stdout, else send NACK and die. */
+void
+authenticate_connection ()
+{
+  int len;
+  char tmp[PATH_MAX];
+  char repository[PATH_MAX];
+  char username[PATH_MAX];
+  char password[PATH_MAX];
+  char *descrambled_password;
+  char server_user[PATH_MAX];
+  struct passwd *pw;
+  int verify_and_exit = 0;
+
+  /* The Authentication Protocol.  Client sends:
+   *
+   *   BEGIN AUTH REQUEST\n
+   *   <REPOSITORY>\n
+   *   <USERNAME>\n
+   *   <PASSWORD>\n
+   *   END AUTH REQUEST\n
+   *
+   * Server uses above information to authenticate, then sends
+   *
+   *   I LOVE YOU\n
+   *
+   * if it grants access, else
+   *
+   *   I HATE YOU\n
+   *
+   * if it denies access (and it exits if denying).
+   *
+   * When the client is "cvs login", the user does not desire actual
+   * repository access, but would like to confirm the password with
+   * the server.  In this case, the start and stop strings are
+   *
+   *   BEGIN VERIFICATION REQUEST\n
+   *
+   *            and
+   *
+   *   END VERIFICATION REQUEST\n
+   *
+   * On a verification request, the server's responses are the same
+   * (with the obvious semantics), but it exits immediately after
+   * sending the response in both cases.
+   *
+   * Why is the repository sent?  Well, note that the actual
+   * client/server protocol can't start up until authentication is
+   * successful.  But in order to perform authentication, the server
+   * needs to look up the password in the special CVS passwd file,
+   * before trying /etc/passwd.  So the client transmits the
+   * repository as part of the "authentication protocol".  The
+   * repository will be redundantly retransmitted later, but that's no
+   * big deal.
+   */
+
+  /* Since we're in the server parent process, error should use the
+     protocol to report error messages.  */
+  error_use_protocol = 1;
+
+  /* Make sure the protocol starts off on the right foot... */
+  fgets (tmp, PATH_MAX, stdin);
+  if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0)
+    verify_and_exit = 1;
+  else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") != 0)
+    error (1, 0, "bad auth protocol start: %s", tmp);
+    
+  /* Get the three important pieces of information in order. */
+  fgets (repository, PATH_MAX, stdin);
+  fgets (username, PATH_MAX, stdin);
+  fgets (password, PATH_MAX, stdin);
+
+  /* Make them pure. */ 
+  strip_trailing_newlines (repository);
+  strip_trailing_newlines (username);
+  strip_trailing_newlines (password);
+
+  /* ... and make sure the protocol ends on the right foot. */
+  fgets (tmp, PATH_MAX, stdin);
+  if (strcmp (tmp,
+              verify_and_exit ?
+              "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n")
+           != 0)
+    {
+      error (1, 0, "bad auth protocol end: %s", tmp);
+    }
+
+  /* We need the real cleartext before we hash it. */
+  descrambled_password = descramble (password);
+
+  if (check_password (username, descrambled_password, repository))
+    {
+      printf ("I LOVE YOU\n");
+      fflush (stdout);
+      memset (descrambled_password, 0, strlen (descrambled_password));
+      free (descrambled_password);
+    }
+  else
+    {
+      printf ("I HATE YOU\n");
+      fflush (stdout);
+      memset (descrambled_password, 0, strlen (descrambled_password));
+      free (descrambled_password);
+      exit (1);
+    }
+  
+  /* Don't go any farther if we're just responding to "cvs login". */
+  if (verify_and_exit)
+    exit (0);
+
+  /* Switch to run as this user. */
+  pw = getpwnam (username);
+  if (pw == NULL)
+    {
+      error (1, 0,
+             "fatal error, aborting.\nerror 0 %s: no such user\n",
+             username);
+    }
+  
+  initgroups (pw->pw_name, pw->pw_gid);
+  setgid (pw->pw_gid);
+  setuid (pw->pw_uid);
+  /* Inhibit access by randoms.  Don't want people randomly
+     changing our temporary tree before we check things in.  */
+  umask (077);
+  
+#if HAVE_PUTENV
+  /* Set LOGNAME and USER in the environment, in case they are
+     already set to something else.  */
+  {
+    char *env;
+    
+    env = xmalloc (sizeof "LOGNAME=" + strlen (username));
+    (void) sprintf (env, "LOGNAME=%s", username);
+    (void) putenv (env);
+    
+    env = xmalloc (sizeof "USER=" + strlen (username));
+    (void) sprintf (env, "USER=%s", username);
+    (void) putenv (env);
+  }
+#endif /* HAVE_PUTENV */
+}
+
+#endif /* AUTH_SERVER_SUPPORT */
+
+
 #endif /* SERVER_SUPPORT */
+