From 9fc47a17c64634c91b707b33f46f9649bb622dd7 Mon Sep 17 00:00:00 2001 From: Thorsten Lockert Date: Tue, 30 Jan 1996 01:10:57 +0000 Subject: Integrate local changes --- gnu/usr.bin/cvs/Makefile.bsd-wrapper | 6 +- gnu/usr.bin/cvs/Makefile.in | 9 +- gnu/usr.bin/cvs/contrib/pcl-cvs/compile-all.el | 52 -- gnu/usr.bin/cvs/examples/comb | 19 - gnu/usr.bin/cvs/examples/uncom | 20 - gnu/usr.bin/cvs/lib/error.c | 207 ----- gnu/usr.bin/cvs/lib/error.h | 52 -- gnu/usr.bin/cvs/src/commit.c | 336 ++++++--- gnu/usr.bin/cvs/src/cvs.h | 174 ++++- gnu/usr.bin/cvs/src/lock.c | 173 ++++- gnu/usr.bin/cvs/src/main.c | 122 ++- gnu/usr.bin/cvs/src/patchlevel.h | 1 - gnu/usr.bin/cvs/src/server.c | 998 ++++++++++++++++++++++--- 13 files changed, 1530 insertions(+), 639 deletions(-) delete mode 100644 gnu/usr.bin/cvs/contrib/pcl-cvs/compile-all.el delete mode 100644 gnu/usr.bin/cvs/examples/comb delete mode 100644 gnu/usr.bin/cvs/examples/uncom delete mode 100644 gnu/usr.bin/cvs/lib/error.c delete mode 100644 gnu/usr.bin/cvs/lib/error.h delete mode 100644 gnu/usr.bin/cvs/src/patchlevel.h (limited to 'gnu/usr.bin/cvs') diff --git a/gnu/usr.bin/cvs/Makefile.bsd-wrapper b/gnu/usr.bin/cvs/Makefile.bsd-wrapper index 256db80da89..da2104a3fe5 100644 --- a/gnu/usr.bin/cvs/Makefile.bsd-wrapper +++ b/gnu/usr.bin/cvs/Makefile.bsd-wrapper @@ -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 \ diff --git a/gnu/usr.bin/cvs/Makefile.in b/gnu/usr.bin/cvs/Makefile.in index 6509798bd78..2f0016581bc 100644 --- a/gnu/usr.bin/cvs/Makefile.in +++ b/gnu/usr.bin/cvs/Makefile.in @@ -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 index 65632775085..00000000000 --- a/gnu/usr.bin/cvs/contrib/pcl-cvs/compile-all.el +++ /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 index 8602856dde5..00000000000 --- a/gnu/usr.bin/cvs/examples/comb +++ /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 index b7ab2776f98..00000000000 --- a/gnu/usr.bin/cvs/examples/uncom +++ /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 index 72f382678c0..00000000000 --- a/gnu/usr.bin/cvs/lib/error.c +++ /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 - -#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 -#define VA_START(args, lastarg) va_start(args, lastarg) -#else -#include -#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 -#include -#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 index a963e115f0b..00000000000 --- a/gnu/usr.bin/cvs/lib/error.h +++ /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_ */ diff --git a/gnu/usr.bin/cvs/src/commit.c b/gnu/usr.bin/cvs/src/commit.c index c37ed7155c7..1973e0ce256 100644 --- a/gnu/usr.bin/cvs/src/commit.c +++ b/gnu/usr.bin/cvs/src/commit.c @@ -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); } diff --git a/gnu/usr.bin/cvs/src/cvs.h b/gnu/usr.bin/cvs/src/cvs.h index 50bef89e568..e9b0652dd09 100644 --- a/gnu/usr.bin/cvs/src/cvs.h +++ b/gnu/usr.bin/cvs/src/cvs.h @@ -21,7 +21,11 @@ #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 +/* Under OS/2, doesn't define popen()/pclose(). */ +#ifdef USE_OWN_POPEN +#include "popen.h" +#endif + #ifdef STDC_HEADERS #include #else @@ -75,7 +84,7 @@ extern char *getenv(); char *strerror (); #endif -#include /* This is supposed to be available on Posix systems */ +#include /* This is supposed to be available on Posix systems */ #include #include @@ -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)); + 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)); + 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)); - + /* 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)); + +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)); + +#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 *)); diff --git a/gnu/usr.bin/cvs/src/lock.c b/gnu/usr.bin/cvs/src/lock.c index 59216f300c9..ffdd351df38 100644 --- a/gnu/usr.bin/cvs/src/lock.c +++ b/gnu/usr.bin/cvs/src/lock.c @@ -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); } + +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); +} diff --git a/gnu/usr.bin/cvs/src/main.c b/gnu/usr.bin/cvs/src/main.c index 9cefce0cc84..74613d71103 100644 --- a/gnu/usr.bin/cvs/src/main.c +++ b/gnu/usr.bin/cvs/src/main.c @@ -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 @@ -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 index 50d3863dcd4..00000000000 --- a/gnu/usr.bin/cvs/src/patchlevel.h +++ /dev/null @@ -1 +0,0 @@ -#define PATCHLEVEL 2 diff --git a/gnu/usr.bin/cvs/src/server.c b/gnu/usr.bin/cvs/src/server.c index 6bef7d855ad..c87e2f5694b 100644 --- a/gnu/usr.bin/cvs/src/server.c +++ b/gnu/usr.bin/cvs/src/server.c @@ -1,4 +1,8 @@ +#include #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)); -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; } +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 () } } +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; +} + 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*/ } +/* 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); + } +} + 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; } +#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 */ + 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); } @@ -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); +} + 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; } + +#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 */ + + +/* + * 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 + * \n + * \n + * \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 */ + -- cgit v1.2.3