[comp.sources.unix] v22i017: Brian Berliner's concurrent RCS system, Part03/07

rsalz@uunet.uu.net (Rich Salz) (05/04/90)

Submitted-by: Brian Berliner <berliner@prisma.com>
Posting-number: Volume 22, Issue 17
Archive-name: cvs-berliner/part03

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 3 (of 7)."
# Contents:  src/checkin.csh src/collect_sets.c src/main.c
#   src/modules.c src/subr.c src/version_number.c
# Wrapped by rsalz@litchi.bbn.com on Thu May  3 16:59:02 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'src/checkin.csh' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/checkin.csh'\"
else
echo shar: Extracting \"'src/checkin.csh'\" \(7570 characters\)
sed "s/^X//" >'src/checkin.csh' <<'END_OF_FILE'
X#!/bin/csh
X#
X# $Id: checkin.csh,v 1.8 89/11/20 13:37:33 berliner Exp $
X#
X#   Copyright (c) 1989, Brian Berliner
X#
X#   You may distribute under the terms of the GNU General Public License
X#   as specified in the README file that comes with the CVS 1.0 kit.
X#
X#############################################################################
X#									    #
X# This script is used to check in sources from vendors.  		    #
X#									    #
X#	Usage: checkin repository Vendor_Tag Vendor_Release_Tag		    #
X#									    #
X# The repository is the directory where the sources should		    #
X# be deposited, the Vendor_Tag is the symbolic tag for the 		    #
X# vendor branch of the RCS release tree, and the Vendor_Release_Tag	    #
X# is the symbolic tag for this release.  				    #
X#									    #
X# checkin traverses the current directory, ensuring that an 		    #
X# identical directory structure exists in the repository directory.  It	    #
X# then checks the files in in the following manner:			    #
X#									    #
X#		1) If the file doesn't yet exist, check it in 		    #
X#			as revision 1.1					    #
X#		2) Tag branch 1.1.1 with the vendor tag			    #
X#		3) Check the file into the vendor branch, 		    #
X#			labeling it with the Vendor_Release_Tag		    #
X#		4) If the file didn't previously exist, 		    #
X#			make the vendor branch the default branch	    #
X#									    #
X# The script also is somewhat verbose in letting the user know what is	    #
X# going on.  It prints a diagnostic when it creates a new file, or updates  #
X# a file that has been modified on the trunk.		   		    #
X#									    #
X#############################################################################
X
Xset vbose = 0
Xset message = ""
Xset cvsbin = /usr/local/bin
Xset rcsbin = /usr/local/bin
Xset grep = /bin/grep
Xset message_file = /tmp/checkin.$$
Xset got_one = 0
X
Xif ( $#argv < 3 ) then
X    echo "Usage: checkin [-v] [-m message] [-f message_file] repository"
X    echo "	Vendor_Tag Vendor_Release_Tag [Vendor_Release_tag...]"
X    exit 1
Xendif
Xwhile ( $#argv )
X    switch ( $argv[1] )
X        case -v:
X            set vbose = 1
X	    breaksw
X	case -m:
X	    shift
X	    echo $argv[1] > $message_file
X	    set got_one = 1
X	    breaksw
X	case -f:
X	    shift
X	    set message_file = $argv[1]
X	    set got_one = 2
X	    breaksw
X	default:
X	    break
X    endsw
X    shift
Xend
Xif ( $#argv < 3 ) then
X    echo "Usage: checkin [-v] [-m message] [-f message_file] repository"
X    echo "	Vendor_Tag Vendor_Release_Tag [Vendor_Release_tag...]"
X    exit 1
Xendif
Xset repository = $argv[1]
Xshift
Xset vendor = $argv[1]
Xshift
Xset release = $argv[1]
Xshift
Xset extra_release = ( $argv )
X
Xif ( ! $?CVSROOT ) then
X    echo "Please set the environmental variable CVSROOT to the root"
X    echo "	of the tree you wish to update"
X    exit 1
Xendif
X
Xif ( $got_one == 0 ) then
X    echo "Please Edit this file to contain the RCS log information" >$message_file
X    echo "to be associated with this file (please remove these lines)">>$message_file
X    if ( $?EDITOR ) then
X	$EDITOR $message_file > /dev/tty
X    else
X	/usr/ucb/vi $message_file > /dev/tty
X    endif
X    set got_one = 1
Xendif
X
Xumask 2
X
Xset update_dir = ${CVSROOT}/${repository}
Xif ( -d SCCS ) then
X    sccs get SCCS/* >& /dev/null
Xendif
Xif ( -d RCS ) then
X    $rcsbin/co RCS/* >& /dev/null
Xendif
Xforeach name ( * .[a-zA-Z0-9]* )
X    if ( $name == SCCS ) then 
X	continue
X    endif
X    if ( $name == RCS ) then 
X	continue
X    endif
X    if ( $vbose ) then 
X	echo "Updating ${repository}/${name}"
X    endif
X    if ( -d $name ) then
X	if ( ! -d ${update_dir}/${name} ) then
X	    echo "WARNING: Creating new directory ${repository}/${name}"
X	    mkdir ${update_dir}/${name}
X	    if ( $status ) then
X		echo "ERROR: mkdir failed - aborting"
X		exit 1
X	    endif
X	endif
X	chdir $name
X	if ( $status ) then
X	    echo "ERROR: Couldn\'t chdir to $name - aborting" 
X	    exit 1
X	endif
X	if ( $vbose ) then
X	    $cvsbin/checkin -v -f $message_file ${repository}/${name} $vendor $release $extra_release
X	else
X	    $cvsbin/checkin -f $message_file ${repository}/${name} $vendor $release $extra_release
X	endif
X	if ( $status ) then 
X	    exit 1
X	endif
X	chdir ..
X    else
X	if ( ! -f $name ) then
X	    echo "WARNING: $name is neither a regular file" 
X	    echo "	   nor a directory - ignored"
X	    continue
X	endif
X	set file = ${update_dir}/${name},v
X	set new = 0
X	set comment = ""
X	grep -s '\$Log.*\$' ${name}
X	if ( $status == 0 ) then
X	    set myext = ${name:e}
X	    set knownext = 0
X	    foreach xx ( "c" "csh" "e" "f" "h" "l" "mac" "me" "mm" "ms" "p" "r" "red" "s" "sh" "sl" "cl" "ml" "el" "tex" "y" "ye" "yr" "" )
X		if ( "${myext}" == "${xx}" ) then
X		    set knownext = 1
X		    break
X		endif
X	    end
X	    if ( $knownext == 0 ) then
X		echo For file ${file}:
X		grep '\$Log.*\$' ${name}
X		echo -n "Please insert a comment leader for file ${name} > "
X		set comment = $<
X	    endif
X	endif
X	if ( ! -f $file ) then
X	    if ( ! -f ${update_dir}/Attic/${name},v ) then
X	        echo "WARNING: Creating new file ${repository}/${name}"
X		if ( "${comment}" != "" ) then
X		    $rcsbin/rcs -q -i -c"${comment}" -t/dev/null $file
X		endif
X	        $rcsbin/ci -q -u1.1 -t/dev/null $file 
X	        if ( $status ) then
X		    echo "ERROR: Initial check-in of $file failed - aborting"
X		    exit 1
X	        endif
X	        set new = 1
X	    else 
X		set file = ${update_dir}/Attic/${name},v
X		echo "WARNING: Updating ${repository}/Attic/${name}"
X	        set head = `head -1 $file`
X	        set branch = `head -2 $file | ${grep} -w branch`
X	        if ( $#head != 2 || $#branch != 2 ) then
X		    echo "ERROR: corrupted RCS file $file - aborting"
X	        endif
X	        if ( "$head[2]" == "1.1;" && "$branch[2]" != "1.1.1;" ) then
X		    ${rcsbin}/rcsdiff -q -r1.1 $file > /dev/null
X		    if ( ! $status ) then
X		        set new = 1
X		    endif
X	        else
X	            if ( "$branch[2]" != "1.1.1;" ) then
X		        echo -n "WARNING: Updating locally modified file "
X			echo    "${repository}/Attic/${name}"
X	            endif
X	        endif
X	    endif
X	else
X	    set head = `head -1 $file`
X	    set branch = `head -2 $file | ${grep} -w branch`
X	    if ( $#head != 2 || $#branch != 2 ) then
X		echo "ERROR: corrupted RCS file $file - aborting"
X	    endif
X	    if ( "$head[2]" == "1.1;" && "$branch[2]" != "1.1.1;" ) then
X		${rcsbin}/rcsdiff -q -r1.1 $file > /dev/null
X		if ( ! $status ) then
X		    set new = 1
X		endif
X	    else
X	        if ( "$branch[2]" != "1.1.1;" ) then
X		    echo -n "WARNING: Updating locally modified file "
X		    echo    "${repository}/${name}"
X	        endif
X	    endif
X	endif
X	$rcsbin/rcs -q -N${vendor}:1.1.1 $file
X	if ( $status ) then
X	    echo "ERROR: Attempt to set Vendor_Tag in $file failed - aborting"
X	    exit 1
X	endif
X	set lock_failed = 0
X	$rcsbin/rcs -q -l${vendor} $file >& /dev/null
X	if ( $status ) then
X	    set lock_failed = 1
X	endif
X	if ( "${comment}" != "" ) then
X	    $rcsbin/rcs -q -c"${comment}" $file
X	endif
X	$rcsbin/ci -q -f -u${vendor} -N${release} $file < $message_file 
X	if ( $status ) then
X	    echo "ERROR: Check-in of $file failed - aborting"
X	    if ( ! $lock_failed ) then
X	        $rcsbin/rcs -q -u${vendor} $file
X	    endif
X	    exit 1
X	endif
X	foreach tag ( $extra_release )
X	    $rcsbin/rcs -q -N${tag}:${release} $file
X	    if ( $status ) then
X		echo "ERROR: Couldn't add tag $tag to file $file"
X		continue
X	    endif
X	end
X	if ( $new ) then
X	    $rcsbin/rcs -q -b${vendor} $file
X	    if ( $status ) then
X		echo "ERROR: Attempt to change default branch failed - aborting"
X		exit 1
X	    endif
X	endif
X    endif
Xend
Xif ( $got_one == 1 ) rm $message_file
END_OF_FILE
if test 7570 -ne `wc -c <'src/checkin.csh'`; then
    echo shar: \"'src/checkin.csh'\" unpacked with wrong size!
fi
# end of 'src/checkin.csh'
fi
if test -f 'src/collect_sets.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/collect_sets.c'\"
else
echo shar: Extracting \"'src/collect_sets.c'\" \(7811 characters\)
sed "s/^X//" >'src/collect_sets.c' <<'END_OF_FILE'
X#ifndef lint
Xstatic char rcsid[] = "$Id: collect_sets.c,v 1.15 89/11/19 23:19:51 berliner Exp $";
X#endif !lint
X
X/*
X *    Copyright (c) 1989, Brian Berliner
X *
X *    You may distribute under the terms of the GNU General Public License
X *    as specified in the README file that comes with the CVS 1.0 kit.
X *
X * Collect Sets
X *
X *	Collects the interesting file names from the administration and
X *	the repository in a number of variables:
X *							solved by:
X *		Clist	conflict-ridden			(user)
X *		Glist	modified, needs merging		(update)
X *		Mlist	modified, needs checking in	(commit)
X *		Olist	needs checking out		(update)
X *		Alist	to be added			(commit)
X *		Rlist	to be removed			(commit)
X *		Wlist	remove entry			(update)
X *		Llist	locked list			(commit)
X *		Blist	branch list			(commit)
X *		Dlist	directory list			(update)
X *
X *	Returns non-zero on error.
X */
X
X#include <sys/param.h>
X#include "cvs.h"
X
Xextern char update_dir[];
X
XCollect_Sets(argc, argv)
X    int argc;
X    char *argv[];
X{
X    register int i;
X    char tmp[MAXPATHLEN], update_user[MAXPATHLEN];
X    int ret = 0;
X
X    /*
X     * By default, a call here must wipe the slate clean
X     */
X    Clist[0] = Glist[0] = Mlist[0] = Olist[0] = Dlist[0] = '\0';
X    Alist[0] = Rlist[0] = Wlist[0] = Llist[0] = Blist[0] = '\0';
X    for (i = 0; i < argc; i++) {
X	(void) strcpy(User, argv[i]);
X	if (update_dir[0] != '\0')
X	    (void) sprintf(update_user, "%s/%s", update_dir, User);
X	else
X	    (void) strcpy(update_user, User);
X	if (force_tag_match && (Tag[0] != '\0' || Date[0] != '\0'))
X	    Locate_RCS();
X	else
X	    (void) sprintf(Rcs, "%s/%s%s", Repository, User, RCSEXT);
X	if (isdir(User)) {		/* just a directory -- add to Dlist */
X	    (void) strcat(Dlist, " ");
X	    (void) strcat(Dlist, User);
X	    continue;
X	}
X	Version_TS(Rcs, Tag, User);
X	if (VN_User[0] == '\0') {
X	    /*
X	     * No entry available, TS_Rcs is invalid
X	     */
X	    if (VN_Rcs[0] == '\0') {
X		/*
X		 * There is no RCS file either
X		 */
X		if (TS_User[0] == '\0')	{ /* there is no user file */
X		    if (!force_tag_match || !isfile(Rcs)) {
X			warn(0, "nothing known about %s", update_user);
X			ret++;
X		    }
X		} else {		/* there is a user file */
X		    warn(0, "use `cvs add' to create entry for %s",
X			 update_user);
X		    ret++;
X		}
X	    } else {
X		/*
X		 * There is an RCS file
X		 */
X		if (TS_User[0] == '\0') {
X		    /*
X		     * There is no user file; ad it to the Olist
X		     */
X		    (void) strcat(Olist, " ");
X		    (void) strcat(Olist, User);
X		} else {
X		    /*
X		     * There is a user file; print a warning and add it
X		     * to the conflict list, Clist
X		     */
X		    warn(0, "move away %s; it is in the way", update_user);
X		    (void) strcat(Clist, " ");
X		    (void) strcat(Clist, User);
X		    ret++;
X		}
X	    }
X	} else if (VN_User[0] == '0' && VN_User[1] == '\0') {
X	    /*
X	     * An entry for a new-born file; TS_Rcs is dummy
X	     */
X	    if (TS_User[0] == '\0') {
X		/*
X		 * There is no user file, but there should be one;
X		 * add it to the remove entry list.
X		 */
X		warn(0, "warning: new-born %s has disappeared", update_user);
X		(void) strcat(Wlist, " ");
X		(void) strcat(Wlist, User);
X	    } else {
X		/*
X		 * There is a user file
X		 */
X		if (VN_Rcs[0] == '\0') {
X		    /*
X		     * There is no RCS file, so add it to the add entry list
X		     */
X		    (void) strcat(Alist, " ");
X		    (void) strcat(Alist, User);
X		} else {
X		    /*
X		     * There is an RCS file, so someone else must have
X		     * checked one in behind our back; added to the conflict
X		     * list
X		     */
X		    warn(0, "conflict: %s created independently by second party",
X			 update_user);
X		    (void) strcat(Clist, " ");
X		    (void) strcat(Clist, User);
X		    ret++;
X		}
X	    }
X	} else if (VN_User[0] == '-') {
X	    /*
X	     * An entry for a removed file, TS_Rcs is invalid
X	     */
X	    if (TS_User[0] == '\0') {
X		/*
X		 * There is no user file (as it should be)
X		 */
X		(void) sprintf(tmp, "-%s", VN_Rcs);
X		if (strcmp(tmp, "-") == 0) {
X		    /*
X		     * There is no RCS file; this is all-right, but it
X		     * has been removed independently by a second party;
X		     * added to the remove entry list.
X		     */
X		    (void) strcat(Wlist, " ");
X		    (void) strcat(Wlist, User);
X		} else if (strcmp(tmp, VN_User) == 0) {
X		    /*
X		     * The RCS file is the same version as the user file,
X		     * and that's OK; added to the to be removed list
X		     */
X		    (void) strcat(Rlist, " ");
X		    (void) strcat(Rlist, User);
X		} else {
X		    /*
X		     * The RCS file is a newer version than the user file;
X		     * and this is defintely not OK; make it a conflict.
X		     */
X		    warn(0, "conflict: removed %s was modified by second party",
X			 update_user);
X		    (void) strcat(Clist, " ");
X		    (void) strcat(Clist, User);
X		    ret++;
X		}
X	    } else {
X		/*
X		 * The user file shouldn't be there
X		 */
X		warn(0, "%s should be removed and is still there", update_user);
X		ret++;
X	    }
X	} else {
X	    /*
X	     * A normal entry, TS_Rcs is valid
X	     */
X	    if (VN_Rcs[0] == '\0') {
X		/*
X		 * There is no RCS file
X		 */
X		if (TS_User[0] == '\0') {
X		    /*
X		     * There is no user file, so just remove the entry
X		     */
X		    warn(0, "warning: %s is not (any longer) pertinent",
X			 update_user);
X		    (void) strcat(Wlist, " ");
X		    (void) strcat(Wlist, User);
X		} else if (strcmp(TS_User, TS_Rcs) == 0) {
X		    /*
X		     * The user file is still unmodified, so just remove it
X		     * from the entry list
X		     */
X		    if (!force_tag_match || !isfile(Rcs)) {
X			warn(0, "%s is no longer in the repository",
X			     update_user);
X			(void) strcat(Wlist, " ");
X			(void) strcat(Wlist, User);
X		    }
X		} else {
X		    /*
X		     * The user file has been modified and since it is no
X		     * longer in the repository, a conflict is raised
X		     */
X		    if (!force_tag_match) {
X			warn(0, "conflict: %s is modified but no longer in the repository",
X			     update_user);
X			(void) strcat(Clist, " ");
X			(void) strcat(Clist, User);
X			ret++;
X		    }
X		}
X	    } else if (strcmp(VN_Rcs, VN_User) == 0) {
X		/*
X		 * The RCS file is the same version as the user file
X		 */
X		if (TS_User[0] == '\0') {
X		    /*
X		     * There is no user file, so note that it was lost
X		     * and extract a new version
X		     */
X		    if (strcmp(command, "checkout") != 0 &&
X			strcmp(command, "co") != 0 &&
X			strcmp(command, "get") != 0)
X			warn(0, "warning: %s was lost", update_user);
X		    (void) strcat(Olist, " ");
X		    (void) strcat(Olist, User);
X		} else if (strcmp(TS_User, TS_Rcs) == 0) {
X		    /*
X		     * The user file is still unmodified, so nothing
X		     * special at all to do -- no lists updated
X		     */
X		} else {
X		    /*
X		     * The user file appears to have been modified, but
X		     * we call No_Difference to verify that it really
X		     * has been modified -- it updates the Mlist,
X		     * if necessary.
X		     */
X		    (void) No_Difference(0);
X		}
X	    } else {
X		/*
X		 * The RCS file is a newer version than the user file
X		 */
X		if (TS_User[0] == '\0') {
X		    /*
X		     * There is no user file, so just get it
X		     */
X		    if (strcmp(command, "checkout") != 0 &&
X			strcmp(command, "co") != 0 &&
X			strcmp(command, "get") != 0)
X			warn(0, "warning: %s was lost", update_user);
X		    (void) strcat(Olist, " ");
X		    (void) strcat(Olist, User);
X		} else if (strcmp(TS_User, TS_Rcs) == 0) {
X		    /*
X		     * The user file is still unmodified, so just get it
X		     * as well
X		     */
X		    (void) strcat(Olist, " ");
X		    (void) strcat(Olist, User);
X		} else {
X		    /*
X		     * The user file appears to have been modified; we call
X		     * No_Difference to verify this for us, and it updates
X		     * Glist if it has really been modified, and Olist if
X		     * it hasn't
X		     */
X		    (void) No_Difference(1);
X		}
X	    }
X	}
X    }
X    return (ret);
X}
END_OF_FILE
if test 7811 -ne `wc -c <'src/collect_sets.c'`; then
    echo shar: \"'src/collect_sets.c'\" unpacked with wrong size!
fi
# end of 'src/collect_sets.c'
fi
if test -f 'src/main.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/main.c'\"
else
echo shar: Extracting \"'src/main.c'\" \(6993 characters\)
sed "s/^X//" >'src/main.c' <<'END_OF_FILE'
Xchar rcsid[] = "$Id: main.c,v 1.23 89/11/20 00:06:34 berliner Exp $\nPatch level ###\n";
X
X/*
X *    Copyright (c) 1989, Brian Berliner
X *
X *    You may distribute under the terms of the GNU General Public License
X *    as specified in the README file that comes with the CVS 1.0 kit.
X *
X * This is the main C driver for the CVS system.
X *
X * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing
X * the shell-script CVS system that this is based on.
X *
X * Usage:
X *	cvs [options] command [options] [files/modules...]
X *
X * Where "command" is composed of:
X *		checkout	Check out a module/dir/file
X *		update		Brings work tree in sync with repository
X *		commit		Checks files into the repository
X *		diff		Runs diffs between revisions
X *		log		Prints to "rlog" information for files
X *		add		Adds an entry to the repository
X *		remove		Removes an entry from the repository
X *		status		Status info on the revisions
X *		join		Merge two RCS revisions
X *		patch		"patch" format diff listing between releases
X *		tag		Add/delete a symbolic tag to the RCS file
X *
X * Future:
X *		checkin		Adds new *directories* to the repository
X *				Currently being done by an external shell
X *				script.
X *
X * Brian Berliner
X * 4/20/89
X */
X
X#include <sys/param.h>
X#include "cvs.h"
X#include "patchlevel.h"
X
Xchar *progname, *command;
X
Xchar *fileargv[MAXFILEPERDIR];
Xint fileargc;
X
Xint use_editor = TRUE;
Xint cvswrite = !CVSREAD_DFLT;
Xint really_quiet = FALSE;
Xint quiet = FALSE;
Xint force_tag_match = FALSE;
X
X/*
X * Globals for the lists created in Collect_Sets()
X */
Xchar Clist[MAXLISTLEN], Glist[MAXLISTLEN], Mlist[MAXLISTLEN];
Xchar Olist[MAXLISTLEN], Alist[MAXLISTLEN], Rlist[MAXLISTLEN];
Xchar Wlist[MAXLISTLEN], Llist[MAXLISTLEN], Blist[MAXLISTLEN];
Xchar Dlist[MAXLISTLEN];
X
X/*
X * Globals which refer to a particular file
X */
Xchar Repository[MAXPATHLEN];
Xchar User[MAXPATHLEN], Rcs[MAXPATHLEN];
Xchar VN_User[MAXPATHLEN], TS_User[MAXPATHLEN];
Xchar VN_Rcs[MAXPATHLEN], TS_Rcs[MAXPATHLEN];
Xchar Tag[MAXPATHLEN], Date[MAXPATHLEN];
X
X/*
X * Options is used to hold options passed on to system(), while prog
X * is alwys used in the calls to system().
X */
Xchar Options[MAXPATHLEN];
Xchar prog[MAXPROGLEN];
X
X/*
X * Defaults, for the environment variables that are not set
X */
Xchar *Rcsbin = RCSBIN_DFLT;
Xchar *Editor = EDITOR_DFLT;
Xchar *CVSroot = CVSROOT_DFLT;
X
Xmain(argc, argv)
X    int argc;
X    char *argv[];
X{
X    extern char *getenv();
X    register char *cp;
X    register int c;
X    int help = FALSE, err = 0;
X
X    /*
X     * Just save the last component of the path for error messages
X     */
X    if ((progname = rindex(argv[0], '/')) == NULL)
X	progname = argv[0];
X    else
X	progname++;
X
X    /*
X     * Query the environment variables up-front, so that
X     * they can be overridden by command line arguments
X     */
X    if ((cp = getenv(RCSBIN_ENV)) != NULL)
X	Rcsbin = cp;
X    if ((cp = getenv(EDITOR_ENV)) != NULL)
X	Editor = cp;
X    if ((cp = getenv(CVSROOT_ENV)) != NULL)
X	CVSroot = cp;
X    if (getenv(CVSREAD_ENV) != NULL)
X	cvswrite = FALSE;
X
X    optind = 1;
X    while ((c = getopt(argc, argv, "rwvb:e:d:H")) != -1) {
X	switch (c) {
X	case 'r':
X	    cvswrite = FALSE;
X	    break;
X	case 'w':
X	    cvswrite = TRUE;
X	    break;
X	case 'v':
X	    (void) sprintf(index(rcsid, '#'), "%d\n", PATCHLEVEL);
X	    (void) fputs(rcsid, stdout);
X	    (void) fputs("\nCopyright (c) 1989, Brian Berliner\n\n\
XCVS may be copied only under the terms of the GNU General Public License,\n\
Xa copy of which can be found with the CVS 1.0 distribution kit.\n", stdout);
X	    exit(0);
X	    break;
X	case 'b':
X	    Rcsbin = optarg;
X	    break;
X	case 'e':
X	    Editor = optarg;
X	    break;
X	case 'd':
X	    CVSroot = optarg;
X	    break;
X	case 'H':
X	    help = TRUE;
X	    break;
X	case '?':
X	default:
X	    usage();
X	}
X    }
X    argc -= optind;
X    argv += optind;
X    if (argc < 1)
X	usage();
X
X    /*
X     * Specifying just the '-H' flag to the sub-command causes a Usage
X     * message to be displayed.
X     */
X    command = cp = argv[0];
X    if (help == TRUE || (argc > 1 && strcmp(argv[1], "-H") == 0))
X	argc = -1;
X
X    /*
X     * Make standard output line buffered, so that progress can be
X     * monitored when redirected to a file, but only when we're not
X     * running the "patch" command, as it generates lots of output
X     * to stdout -- just leave it block buffered.
X     */
X    if (strcmp(cp, "patch") != 0)
X	setlinebuf(stdout);
X
X    if (strcmp(cp, "update") == 0)
X	err += update(argc, argv);
X    else if (strcmp(cp, "commit") == 0 || strcmp(cp, "ci") == 0)
X	commit(argc, argv);
X    else if (strcmp(cp, "diff") == 0)
X	diff(argc, argv);
X    else if (strcmp(cp, "checkout") == 0 || strcmp(cp, "co") == 0 ||
X		strcmp(cp, "get") == 0)
X	checkout(argc, argv);
X    else if (strcmp(cp, "log") == 0)
X	log(argc, argv);
X    else if (strcmp(cp, "status") == 0)
X	status(argc, argv);
X    else if (strcmp(cp, "add") == 0)
X	add(argc, argv);
X    else if (strcmp(cp, "remove") == 0)
X	remove(argc, argv);
X    else if (strcmp(cp, "join") == 0)
X	join(argc, argv);
X    else if (strcmp(cp, "patch") == 0)
X	patch(argc, argv);
X    else if (strcmp(cp, "tag") == 0)
X	tag(argc, argv);
X    else
X	usage();			/* oops.. no match */
X    Lock_Cleanup(0);
X    exit(err);
X}
X
Xstatic
Xusage()
X{
X    (void) fprintf(stderr,
X	"Usage: %s [cvs-options] command [command-options] [files...]\n",
X		   progname);
X    (void) fprintf(stderr, "\tWhere 'cvs-options' are:\n");
X    (void) fprintf(stderr, "\t\t-r\t\tMake checked-out files read-only\n");
X    (void) fprintf(stderr,
X	"\t\t-w\t\tMake checked-out files read-write (default)\n");
X    (void) fprintf(stderr, "\t\t-v\t\tCVS version and copyright\n");
X    (void) fprintf(stderr, "\t\t-b bindir\tFind RCS programs in 'bindir'\n");
X    (void) fprintf(stderr,
X	"\t\t-e editor\tUse 'editor' for editing log information\n");
X    (void) fprintf(stderr, "\t\t-d CVS_root_directory\n");
X    (void) fprintf(stderr, "\t\t\t\t\Points to the root of the CVS tree\n");
X    (void) fprintf(stderr, "\tand 'command' is:\n");
X    (void) fprintf(stderr, "\t\tcheckout\tCreates a new CVS directory\n");
X    (void) fprintf(stderr,
X	"\t\tupdate\t\tBrings work tree in sync with repository\n");
X    (void) fprintf(stderr,
X	"\t\tcommit\t\tChecks files into the repository\n");
X    (void) fprintf(stderr, "\t\tdiff\t\tRuns diffs between revisions\n");
X    (void) fprintf(stderr,
X	"\t\tlog\t\tPrints out 'rlog' information for files\n");
X    (void) fprintf(stderr, "\t\tstatus\t\tStatus info on the revisions\n");
X    (void) fprintf(stderr, "\t\tadd\t\tAdds an entry to the repository\n");
X    (void) fprintf(stderr,
X	"\t\tremove\t\tRemoves an entry from the repository\n");
X    (void) fprintf(stderr,
X	"\t\tjoin\t\tJoins an RCS revision with the head on checkout\n");
X    (void) fprintf(stderr,
X	"\t\tpatch\t\t\"patch\" format diffs between releases\n");
X    (void) fprintf(stderr, "\t\ttag\t\tAdd a symbolic tag to the RCS file\n");
X    (void) fprintf(stderr,
X	"\tSpecify the '-H' option to each command for Usage information\n");
X    exit(1);
X}
END_OF_FILE
if test 6993 -ne `wc -c <'src/main.c'`; then
    echo shar: \"'src/main.c'\" unpacked with wrong size!
fi
# end of 'src/main.c'
fi
if test -f 'src/modules.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/modules.c'\"
else
echo shar: Extracting \"'src/modules.c'\" \(8254 characters\)
sed "s/^X//" >'src/modules.c' <<'END_OF_FILE'
X#ifndef lint
Xstatic char rcsid[] = "$Id: modules.c,v 1.14 89/11/19 23:20:12 berliner Exp $";
X#endif !lint
X
X/*
X *    Copyright (c) 1989, Brian Berliner
X *
X *    You may distribute under the terms of the GNU General Public License
X *    as specified in the README file that comes with the CVS 1.0 kit.
X *
X * Modules
X *
X *	Functions for accessing the modules file.
X *
X *	The modules file supports basically three formats of lines:
X *		key [options] directory files...
X *		key [options] directory
X *		key -a aliases...
X *
X *	The -a option allows an aliasing step in the parsing of the modules
X *	file.  The "aliases" listed on a line following the -a are
X *	processed one-by-one, as if they were specified as arguments on the
X *	command line.
X */
X
X#include <sys/param.h>
X#include <sys/file.h>
X#include <ndbm.h>
X#include "cvs.h"
X
Xextern int update_build_dirs;
X
Xint run_module_prog = 1;		/* run -i/-o/-t prog by default */
X
X/*
X * Open the modules file, and die if the CVSROOT environment variable
X * was not set.  If the modules file does not exist, that's fine, and
X * a warning message is displayed and a NULL is returned.
X */
XDBM *
Xopen_module()
X{
X    char mfile[MAXPATHLEN];
X    DBM *db;
X
X    if (CVSroot == NULL) {
X	(void) fprintf(stderr, "%s: must set the CVSROOT environment variable\n",
X		       progname);
X	error(0, "or specify the '-d' option to %s", progname);
X    }
X    (void) sprintf(mfile, "%s/%s", CVSroot, CVSROOTADM_MODULES);
X    if ((db = dbm_open(mfile, O_RDONLY, 0666)) == NULL)
X	warn(0, "warning: cannot open modules file %s", mfile);
X    return (db);
X}
X
X/*
X * Close the modules file, if the open succeeded, that is
X */
Xclose_module(db)
X    DBM *db;
X{
X    if (db != NULL) {
X	dbm_close(db);
X    }
X}
X
X/*
X * This is the recursive function that processes a module name.
X * If tag is set, just tag files, otherwise, we were called to check files
X * out.
X */
Xdo_module(db, mname, m_type, msg)
X    DBM *db;
X    char *mname;
X    enum mtype m_type;
X    char *msg;
X{
X    char *checkin_prog = NULL, *checkout_prog = NULL, *tag_prog = NULL;
X    char cwd[MAXPATHLEN], file[MAXPATHLEN];
X    char *moduleargv[MAXFILEPERDIR];
X    int moduleargc, just_file;
X    datum key, val;
X    char *cp, **argv;
X    int c, alias = 0, argc, err = 0;
X
X    just_file = FALSE;
X    update_build_dirs = FALSE;
X    /*
X     * Look the argument module name up in the database; if we found it, use
X     * it, otherwise, see if the file or directory exists relative to the
X     * root directory (CVSroot).  If there is a directory there, it is
X     * extracted or tagged recursively; if there is a file there, it is
X     * extracted or tagged individually; if there is no file or directory
X     * there, we are done.
X     */
X    key.dptr = mname;
X    key.dsize = strlen(key.dptr);
X    if (db != NULL)
X	val = dbm_fetch(db, key);
X    else
X	val.dptr = NULL;
X    if (val.dptr != NULL) {
X	val.dptr[val.dsize] = '\0';
X    } else {
X	/*
X	 * Need to determine if the argument module name is a directory
X	 * or a file relative to the CVS root and set update_build_dirs
X	 * and just_file accordingly
X	 */
X	update_build_dirs = TRUE;
X	(void) sprintf(file, "%s/%s", CVSroot, key.dptr);
X	if (!isdir(file)) {
X	    (void) strcat(file, RCSEXT);
X	    if (!isfile(file)) {
X		warn(0, "cannot find '%s' - ignored", key.dptr);
X		err++;
X		return (err);
X	    } else {
X		update_build_dirs = FALSE;
X		just_file = TRUE;
X	    }
X	}
X	val = key;
X    }
X    /*
X     * If just extracting or tagging files, need to munge the
X     * passed in module name to look like an actual module entry.
X     */
X    if (just_file == TRUE) {
X	if ((cp = rindex(key.dptr, '/')) != NULL) {
X	    *cp++ = '\0';
X	} else {
X	    cp = key.dptr;
X	    key.dptr = ".";
X	}
X	(void) sprintf(file, "%s %s %s", key.dptr, key.dptr, cp);
X    } else {
X	(void) sprintf(file, "%s %s", key.dptr, val.dptr);
X    }
X    line2argv(&moduleargc, moduleargv, file);
X    argc = moduleargc;
X    argv = moduleargv;
X    if (getwd(cwd) == NULL)
X	error(0, "cannot get current working directory: %s", cwd);
X    optind = 1;
X    while ((c = getopt(argc, argv, CVSMODULE_OPTS)) != -1) {
X	switch (c) {
X	case 'a':
X	    alias = 1;
X	    break;
X	case 'i':
X	    checkin_prog = optarg;
X	    break;
X	case 'o':
X	    checkout_prog = optarg;
X	    break;
X	case 't':
X	    tag_prog = optarg;
X	    break;
X	case '?':
X	    warn(0, "modules file has invalid option for key %s value %s",
X		 key.dptr, val.dptr);
X	    err++;
X	    return (err);
X	    break;
X	}
X    }
X    argc -= optind;
X    argv += optind;
X    if (argc == 0) {
X	warn(0, "modules file missing directory for key %s value %s",
X	     key.dptr, val.dptr);
X	err++;
X	return (err);
X    }
X    if (alias) {
X	register int i;
X
X	for (i = 0; i < argc; i++) {
X	    if (!quiet)
X		printf("%s %s: %s %s\n", progname, command, msg, argv[i]);
X	    err += do_module(db, argv[i], m_type, msg);
X	}
X	return (err);
X    }
X    err += process_module(argc, argv, m_type, &key);
X    if (err == 0 && run_module_prog) {
X	if (m_type == CHECKOUT && checkin_prog !=  NULL) {
X	    FILE *fp = open_file(CVSADM_CIPROG, "w+");
X	    (void) fprintf(fp, "%s\n", checkin_prog);
X	    (void) fclose(fp);
X	}
X    }
X    if (chdir(cwd) < 0)
X	error(1, "failed chdir to %s!", cwd);
X    if (err == 0 && run_module_prog) {
X	if ((m_type == TAG && tag_prog != NULL) ||
X	    (m_type == CHECKOUT && checkout_prog != NULL)) {
X	    (void) sprintf(prog, "%s %s",
X			   m_type == TAG ? tag_prog : checkout_prog, key.dptr);
X	    if (!quiet)
X		printf("%s %s: Executing '%s'\n", progname, command, prog);
X	    err += system(prog);
X	}
X    }
X    free_names(&moduleargc, moduleargv);
X    return (err);
X}
X
Xstatic
Xprocess_module(argc, argv, m_type, keyp)
X    int argc;
X    char *argv[];
X    enum mtype m_type;
X    datum *keyp;
X{
X    register int i, just_file;
X    int err = 0;
X
X    just_file = argc > 1;
X    if (!just_file && update_build_dirs == FALSE && argc == 1)
X	update_build_dirs = TRUE;
X    if (m_type == TAG || m_type == PATCH) {
X	(void) sprintf(Repository, "%s/%s", CVSroot, argv[0]);
X	if (chdir(Repository) < 0) {
X	    warn(1, "cannot chdir to %s", Repository);
X	    err++;
X	    return (err);
X	}
X    } else {
X	if (Build_Dirs_and_chdir(keyp->dptr) != 0) {
X	    warn(0, "ignoring module %s", keyp->dptr);
X	    err++;
X	    return (err);
X	}
X	(void) sprintf(Repository, "%s/%s", CVSroot, argv[0]);
X	if (!isdir(CVSADM)) {
X	    FILE *fp;
X
X	    Create_Admin(Repository, DFLT_RECORD);
X	    if (just_file == TRUE) {
X		fp = open_file(CVSADM_ENTSTAT, "w+");
X		(void) fclose(fp);
X	    }
X	} else {
X	    char file[MAXPATHLEN];
X
X	    (void) strcpy(file, Repository);
X	    Name_Repository();
X	    if (strcmp(Repository, file) != 0) {
X		warn(0, "existing repository %s does not match %s",
X		     Repository, file);
X		err++;
X		return (err);
X	    }
X	}
X    }
X    if (update_build_dirs == TRUE) {
X	extern char update_dir[];
X
X	(void) strcpy(update_dir, keyp->dptr);
X	if (m_type == CHECKOUT)
X	    err += update(0, (char **)0);
X	else if (m_type == TAG)
X	    err += tagit((char *)0);
X	else if (m_type == PATCH)
X	    err += patched((char *)0);
X	else
X	    error(0, "impossible module type %d", (int)m_type);
X	return (err);
X    }
X    argc--;
X    argv++;
X    for (i = 0; i < argc; i++) {
X	char line[MAXLINELEN];
X
X	(void) strcpy(User, argv[i]);
X	(void) sprintf(Rcs, "%s/%s%s", Repository, User, RCSEXT);
X	if (m_type == CHECKOUT) {
X	    Version_TS(Rcs, Tag, User);
X	    if (TS_User[0] == '\0') {
X		(void) sprintf(line, "Initial %s", User);
X		Register(User, VN_Rcs, line);
X	    }
X	} else if (m_type == TAG) {
X	    err += tagit(Rcs);
X	} else if (m_type == PATCH) {
X	    err += patched(Rcs);
X	} else {
X	    error(0, "impossible module type %d", (int)m_type);
X	}
X    }
X    if (m_type == CHECKOUT)
X	err += update(++argc, --argv);
X    return (err);
X}
X
Xcat_module()
X{
X    FILE *fp;
X    DBM *db;
X    datum key, val;
X
X    if ((db = open_module()) == NULL)
X	error(0, "failed to cat the modules file");
X    if ((fp = popen(SORT, "w")) == NULL)
X	fp = stdout;
X    for (key = dbm_firstkey(db); key.dptr != NULL; key = dbm_nextkey(db)) {
X	key.dptr[key.dsize] = '\0';
X	(void) fprintf(fp, "%-20s", key.dptr);
X	val = dbm_fetch(db, key);
X	if (val.dptr != NULL) {
X	    val.dptr[val.dsize] = '\0';
X	    (void) fprintf(fp, " %s\n", val.dptr);
X	} else {
X	    (void) fprintf(fp, "\n");
X	}
X    }
X    if (fp != stdout)
X	(void) pclose(fp);
X}
END_OF_FILE
if test 8254 -ne `wc -c <'src/modules.c'`; then
    echo shar: \"'src/modules.c'\" unpacked with wrong size!
fi
# end of 'src/modules.c'
fi
if test -f 'src/subr.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/subr.c'\"
else
echo shar: Extracting \"'src/subr.c'\" \(8199 characters\)
sed "s/^X//" >'src/subr.c' <<'END_OF_FILE'
X#ifndef lint
Xstatic char rcsid[] = "$Id: subr.c,v 1.14 89/11/20 09:51:10 berliner Exp $";
X#endif !lint
X
X/*
X *    Copyright (c) 1989, Brian Berliner
X *
X *    You may distribute under the terms of the GNU General Public License
X *    as specified in the README file that comes with the CVS 1.0 kit.
X *
X * Various useful functions for the CVS support code.
X */
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/file.h>
X#include <varargs.h>
X#include "cvs.h"
X
X/*
X * Send a "printf" format string to stderr and die, calling the
X * defined exit function first, if necessary
X */
Xerror(doperror, fmt, va_alist)
X    int doperror;
X    char *fmt;
X    va_dcl
X{
X    extern int errno;
X    va_list x1;
X    int err = errno;
X
X    va_start(x1);
X    (void) fprintf(stderr, "%s: ", progname);
X    (void) vfprintf(stderr, fmt, x1);
X    if (doperror) {
X	(void) fprintf(stderr, ": ");
X	errno = err;
X	perror("");
X	errno = 0;
X    } else
X	(void) fprintf(stderr, "\n");
X    va_end(x1);
X    Lock_Cleanup(0);
X    exit(1);
X}
X
X/*
X * Like error() above, but just display the message to stderr,
X * without dying or running the exit function.
X */
Xwarn(doperror, fmt, va_alist)
X    int doperror;
X    char *fmt;
X    va_dcl
X{
X    extern int errno;
X    va_list x1;
X    int err = errno;
X
X    va_start(x1);
X    (void) fprintf(stderr, "%s: ", progname);
X    (void) vfprintf(stderr, fmt, x1);
X    if (doperror) {
X	(void) fprintf(stderr, ": ");
X	errno = err;
X	perror("");
X	errno = 0;
X    } else
X	(void) fprintf(stderr, "\n");
X    va_end(x1);
X}
X
X/*
X * Copies "from" to "to".
X * mallocs a buffer large enough to hold the entire file and
X * does one read/one write to do the copy.  This is reasonable,
X * since source files are typically not too large.
X */
Xcopy_file(from, to)
X    char *from;
X    char *to;
X{
X    struct stat sb;
X    int fdin, fdout;
X    char *buf;
X
X    if ((fdin = open(from, O_RDONLY)) < 0)
X	error(1, "cannot open %s for copying", from);
X    if (fstat(fdin, &sb) < 0)
X	error(1, "cannot fstat %s", from);
X    if ((fdout = creat(to, (int) sb.st_mode & 07777)) < 0)
X	error(1, "cannot create %s for copying", to);
X    if (sb.st_size > 0) {
X	buf = xmalloc((int)sb.st_size);
X	if (read(fdin, buf, (int)sb.st_size) != (int)sb.st_size)
X	    error(1, "cannot read file %s for copying", from);
X	if (write(fdout, buf, (int)sb.st_size) != (int)sb.st_size)
X	    error(1, "cannot write file %s for copying", to);
X	free(buf);
X    }
X    (void) close(fdin);
X    (void) close(fdout);
X}
X
X/*
X * Returns non-zero if the argument file is a directory, or
X * is a symbolic link which points to a directory.
X */
Xisdir(file)
X    char *file;
X{
X    struct stat sb;
X
X    if (stat(file, &sb) < 0)
X	return (0);
X    return ((sb.st_mode & S_IFMT) & S_IFDIR);
X}
X
X/*
X * Returns non-zero if the argument file is a symbolic link.
X */
Xislink(file)
X    char *file;
X{
X    struct stat sb;
X
X    if (lstat(file, &sb) < 0)
X	return (0);
X    return ((sb.st_mode & S_IFMT) & S_IFLNK);
X}
X
X/*
X * Returns non-zero if the argument file exists.
X */
Xisfile(file)
X    char *file;
X{
X    struct stat sb;
X
X    if (stat(file, &sb) < 0)
X	return (0);
X    return (1);
X}
X
X/*
X * Returns non-zero if the argument file is readable.
X * XXX - muct be careful if "cvs" is ever made setuid!
X */
Xisreadable(file)
X    char *file;
X{
X    return (access(file, R_OK) != -1);
X}
X
X/*
X * Returns non-zero if the argument file is writable
X * XXX - muct be careful if "cvs" is ever made setuid!
X */
Xiswritable(file)
X    char *file;
X{
X    return (access(file, W_OK) != -1);
X}
X
X/*
X * Open a file and die if it fails
X */
XFILE *
Xopen_file(name, mode)
X    char *name;
X    char *mode;
X{
X    FILE *fp;
X
X    if ((fp = fopen(name, mode)) == NULL)
X	error(1, "cannot open %s", name);
X    return (fp);
X}
X
X/*
X * Make a directory and die if it fails
X */
Xmake_directory(name)
X    char *name;
X{
X    if (mkdir(name, 0777) < 0)
X	error(1, "cannot make directory %s", name);
X}
X
X/*
X * malloc some data and die if it fails
X */
Xchar *
Xxmalloc(bytes)
X    int bytes;
X{
X    extern char *malloc();
X    char *cp;
X
X    if (bytes <= 0)
X	error(0, "bad malloc size %d", bytes);
X    if ((cp = malloc((unsigned)bytes)) == NULL)
X	error(0, "malloc failed");
X    return (cp);
X}
X
X
X/*
X * ppstrcmp() is a front-end for strcmp() when the arguments
X * are pointers to pointers to chars.
X */
Xppstrcmp(pp1, pp2)
X    register char **pp1, **pp2;
X{
X    return (strcmp(*pp1, *pp2));
X}
X
X/*
X * ppstrcmp_files() is a front-end for strcmp() when the arguments
X * are pointers to pointers to chars.
X * For some reason, the ppstrcmp() above sorts in reverse order when
X * called from Entries2Files().
X */
Xppstrcmp_files(pp1, pp2)
X    register char **pp1, **pp2;
X{
X    /*
X     * Reversing the arguments here cause for the
X     * correct alphabetical order sort, as we desire.
X     */
X    return (strcmp(*pp2, *pp1));
X}
X
X/* Some UNIX distributions don't include these in their stat.h */
X#ifndef S_IWRITE
X#define	S_IWRITE	0000200		/* write permission, owner */
X#endif !S_IWRITE
X#ifndef S_IWGRP
X#define	S_IWGRP		0000020		/* write permission, grougroup */
X#endif !S_IWGRP
X#ifndef S_IWOTH
X#define	S_IWOTH		0000002		/* write permission, other */
X#endif !S_IWOTH
X
X/*
X * Change the mode of a file, either adding write permissions, or
X * removing all write permissions.  Adding write permissions honors
X * the current umask setting.
X */
Xxchmod(fname, writable)
X    char *fname;
X    int writable;
X{
X    struct stat sb;
X    int mode, oumask;
X
X    if (stat(fname, &sb) < 0) {
X	warn(1, "cannot stat %s", fname);
X	return;
X    }
X    if (writable) {
X	oumask = umask(0);
X	(void) umask(oumask);
X	mode = sb.st_mode | ((S_IWRITE|S_IWGRP|S_IWOTH) & ~oumask);
X    } else {
X	mode = sb.st_mode & ~(S_IWRITE|S_IWGRP|S_IWOTH);
X    }
X    if (chmod(fname, mode) < 0)
X	warn(1, "cannot change mode of file %s", fname);
X}
X
X/*
X * Rename a file and die if it fails
X */
Xrename_file(from, to)
X    char *from;
X    char *to;
X{
X    if (rename(from, to) < 0)
X	error(1, "cannot rename file %s to %s", from, to);
X}
X
X/*
X * Compare "file1" to "file2".
X * Return non-zero if they don't compare exactly.
X *
X * mallocs a buffer large enough to hold the entire file and
X * does two reads to load the buffer and calls bcmp to do the cmp.
X * This is reasonable, since source files are typically not too large.
X */
Xxcmp(file1, file2)
X    char *file1;
X    char *file2;
X{
X    register char *buf1, *buf2;
X    struct stat sb;
X    off_t size;
X    int ret, fd1, fd2;
X
X    if ((fd1 = open(file1, O_RDONLY)) < 0)
X	error(1, "cannot open file %s for comparing", file1);
X    if ((fd2 = open(file2, O_RDONLY)) < 0)
X	error(1, "cannot open file %s for comparing", file2);
X    if (fstat(fd1, &sb) < 0)
X	error(1, "cannot fstat %s", file1);
X    size = sb.st_size;
X    if (fstat(fd2, &sb) < 0)
X	error(1, "cannot fstat %s", file2);
X    if (size == sb.st_size) {
X	if (size == 0)
X	    ret = 0;
X	else {
X	    buf1 = xmalloc((int)size);
X	    buf2 = xmalloc((int)size);
X	    if (read(fd1, buf1, (int)size) != (int)size)
X		error(1, "cannot read file %s cor comparing", file1);
X	    if (read(fd2, buf2, (int)size) != (int)size)
X		error(1, "cannot read file %s for comparing", file2);
X	    ret = bcmp(buf1, buf2, (int)size);
X	    free(buf1);
X	    free(buf2);
X	}
X    } else
X	ret = 1;
X    (void) close(fd1);
X    (void) close(fd2);
X    return (ret);
X}
X
X/*
X * Recover the space allocated by Find_Names() and line2argv()
X */
Xfree_names(pargc, argv)
X    int *pargc;
X    char *argv[];
X{
X    register int i;
X
X    for (i = 0; i < *pargc; i++) {	/* only do through *pargc */
X	free(argv[i]);
X    }
X    *pargc = 0;				/* and set it to zero when done */
X}
X
X/*
X * Convert a line into argc/argv components and return the result in
X * the arguments as passed.  Use free_names() to return the memory
X * allocated here back to the free pool.
X */
Xline2argv(pargc, argv, line)
X    int *pargc;
X    char *argv[];
X    char *line;
X{
X    char *cp;
X
X    *pargc = 0;
X    for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
X	argv[*pargc] = xmalloc(strlen(cp) + 1);
X	(void) strcpy(argv[*pargc], cp);
X	(*pargc)++;
X    }
X}
X
X/*
X * Returns the number of dots ('.') found in an RCS revision number
X */
Xnumdots(s)
X    char *s;
X{
X    char *cp;
X    int dots = 0;
X
X    for (cp = s; *cp; cp++) {
X	if (*cp == '.')
X	    dots++;
X    }
X    return (dots);
X}
END_OF_FILE
if test 8199 -ne `wc -c <'src/subr.c'`; then
    echo shar: \"'src/subr.c'\" unpacked with wrong size!
fi
# end of 'src/subr.c'
fi
if test -f 'src/version_number.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/version_number.c'\"
else
echo shar: Extracting \"'src/version_number.c'\" \(10526 characters\)
sed "s/^X//" >'src/version_number.c' <<'END_OF_FILE'
X#ifndef lint
Xstatic char rcsid[] = "$Id: version_number.c,v 1.16 89/11/19 23:20:35 berliner Exp $";
X#endif !lint
X
X/*
X *    Copyright (c) 1989, Brian Berliner
X *
X *    You may distribute under the terms of the GNU General Public License
X *    as specified in the README file that comes with the CVS 1.0 kit.
X *
X * Version Number
X *
X *	Returns the requested version number of the RCS file, satisfying tags
X *	and walking branches, if necessary.
X *
X *	"rcs" is the full pathname to the ,v file which is read to determine
X *	the current head version number for the RCS file.  Things are
X *	complicated by needing to walk branches and symbolic tags.  The
X *	algorithm used here for branches is not quite the same as that
X *	included with RCS, but should work 99.999% of the time (or more).
X *
X *	The result is placed in "vers"; null-string if error.
X */
X
X#include <ctype.h>
X#include "cvs.h"
X
XVersion_Number(rcs, tag, date, vers)
X    char *rcs;
X    char *tag;
X    char *date;
X    char *vers;
X{
X    FILE *fpin;
X    char rev[50];
X
X    vers[0] = rev[0] = '\0';		/* Assume failure */
X    /*
X     * Open the RCS file.  If it failed, just return, as the version
X     * is already nulled.
X     */
X    if ((fpin = fopen(rcs, "r")) == NULL)
X	return;
X    get_version(fpin, tag, date, rev, vers);
X    if (vers[0] == '\0' && tag[0] != '\0') {
X	if (!isdigit(tag[0])) {
X	    char *cp, temp[MAXLINELEN];
X	    extern char update_dir[];
X
X	    if (CVSroot[0] != '\0' &&
X		strncmp(rcs, CVSroot, strlen(CVSroot)) == 0) {
X		cp = rcs + strlen(CVSroot) + 1;
X	    } else {
X		if ((cp = index(rcs, '/')) == NULL && update_dir[0] != '\0') {
X		    (void) sprintf(temp, "%s/%s", update_dir, rcs);
X		    cp = temp;
X		} else {
X		    cp = rcs;
X		}
X	    }
X	    if (force_tag_match) {
X		if (!quiet)
X		    warn(0, "tag %s undefined in %s; ignored", tag, cp);
X	    } else {
X		if (!quiet)
X		    warn(0, "tag %s undefined in %s; using %s", tag, cp, rev);
X		(void) strcpy(vers, rev);
X	    }
X	} else {
X		(void) strcpy(vers, rev);
X	}
X    }
X    (void) fclose(fpin);
X}
X
X/*
X * The main driver for doing the work of getting the required revision
X * number.  Returns computed revision in "rev" or "vers" depending
X * on whether the "tag" could be satisfied or not.
X */
Xstatic
Xget_version(fp, tag, date, rev, vers)
X    FILE *fp;
X    char *tag;
X    char *date;
X    char *rev;
X    char *vers;
X{
X    char line[MAXLINELEN];
X    char *cp;
X    int symtag_matched = 0;
X
X    /*
X     * Scan to find the current "head" setting,
X     * which really isn't the head if the RCS file is using a branch
X     * for the head, sigh.
X     *
X     * Assumption here is that the "head" line is always first.
X     */
X    if (fgets(line, sizeof(line), fp) == NULL)
X	return;
X    if (strncmp(line, RCSHEAD, sizeof(RCSHEAD) - 1) != 0 ||
X	(cp = rindex(line, ';')) == NULL)
X	return;
X    *cp = '\0';				/* strip the ';' */
X    if ((cp = rindex(line, ' ')) == NULL &&
X	(cp = rindex(line, '\t')) == NULL)
X	return;
X    cp++;
X    (void) strcpy(rev, cp);
X    /*
X     * The "rev" string now contains the value of the RCS "head".
X     * Read the next line to find out if we should walk the branches.
X     *
X     * Assumption here is that "branch" is always on the second line
X     * of the RCS file.  If a "branch" line does not exist, we assume
X     * it is an old format RCS file, and blow it off.
X     */
X    if (fgets(line, sizeof(line), fp) == NULL)
X	return;
X    if (strncmp(line, RCSBRANCH, sizeof(RCSBRANCH) - 1) == 0 &&
X	(cp = rindex(line, ';')) != NULL) {
X	*cp = '\0';			/* strip the ';' */
X	if ((cp = rindex(line, ' ')) == NULL &&
X	    (cp = rindex(line, '\t')) == NULL)
X	    return;
X	cp++;
X	if (*cp != NULL)
X	    (void) strcpy(rev, cp);
X    }
X    /*
X     * "rev" now contains either the "head" value, or the "branch"
X     * value (if it was set).  If we're looking for a particular symbolic
X     * or numeric tag, we must find the symbol, and then do
X     * branch completion as usual, if necessary.
X     */
X    if (date[0] != '\0') {
X	get_date(fp, date, rev, vers);
X	return;
X    }
X    if (tag[0] != '\0') {
X	/* return of 0 means we found an exact match, or there was an error */
X	if ((symtag_matched = get_tag(fp, tag, rev, vers)) == 0)
X	    return;
X    }
X    /*
X     * "rev" now contains either the "head" value, or the tag value,
X     * or the "branch" value.  get_branch() will fill in "rev" with
X     * the highest numbered branch off "rev", if necessary.
X     */
X    get_branch(fp, rev);
X    if (tag[0] == '\0' || isdigit(tag[0]) || symtag_matched < 0)
X	(void) strcpy(vers, rev);
X}
X
X/*
X * We were requested to find a particular symbolic or numeric revision.
X * So, scan for the "symbols" line in the RCS file, or for the first
X * match of a line if the tag is numeric.
X *
X * Would really like to use strtok() here, but callers in update() are
X * already using it.
X *
X * Return value of 0 means to use "vers" as it stands, while a return value
X * of 1 means to use "rev", but to check for possible branch completions
X * to find the head of a branch.
X */
Xstatic
Xget_tag(fp, tag, rev, vers)
X    FILE *fp;
X    char *tag;
X    char *rev;
X    char *vers;
X{
X    char line[MAXLINELEN], tagdot[50];
X    char *cp, *cprev;
X    int tagdots, tagdotlen;
X
X    if (isdigit(tag[0])) {
X	while (tag[strlen(tag)] == '.')
X	    tag[strlen(tag)] = '\0';	/* strip trailing dots */
X	(void) sprintf(tagdot, "%s.", tag);
X	tagdotlen = strlen(tagdot);
X	tagdots = numdots(tag);
X    }
X    while (fgets(line, sizeof(line), fp) != NULL) {
X	if (strncmp(line, RCSDESC, sizeof(RCSDESC) - 1) == 0) {
X	    /*
X	     * Use head, or a partial branch match found with the strncmp
X	     * call with tagdot below
X	     */
X	    rewind(fp);
X	    return (1);
X	}
X	/*
X	 * For numeric tags, the RCS file contains the revision
X	 * number all by itself on a single line, so we check for
X	 * that match here.
X	 */
X	if (isdigit(tag[0])) {
X	    if ((cp = rindex(line, '\n')) != NULL)
X		*cp = '\0';
X	    if (strcmp(tag, line) == 0) {
X		(void) strcpy(vers, line);
X		return (0);		/* a match for a numeric tag */
X	    }
X	    if (strncmp(tagdot, line, tagdotlen) == 0) {
X		if ((tagdots & 1) == 0 && numdots(line) == tagdots+1)
X		    (void) strcpy(rev, line);
X	    }
X	    continue;
X	}
X	if (strncmp(line, RCSSYMBOL, sizeof(RCSSYMBOL)-1)!=0 ||
X	    (cp = rindex(line, ';')) == NULL)
X	    continue;
X	*cp = ' ';			/* strip the ';' */
X	if ((cp = index(line, ' ')) == NULL &&
X	    (cp = index(line, '\t')) == NULL)
X	    continue;
X	/*
X	 * A rather ugly loop to process the "symbols" line.  I would
X	 * really rather use strtok(), but other above me already are,
X	 * and strtok() blows up in this case.
X	 */
X	while (cp && *cp) {
X	    while (isspace(*cp))
X		cp++;
X	    /* symbols and revisions are separated by a colon */
X	    if ((cprev = index(cp, ':')) == NULL) {
X		while (*cp && !isspace(*cp))
X		    cp++;
X		continue;
X	    }
X	    *cprev++ = '\0';
X	    /*
X	     * "cp" points to the NULL-terminated symbolic name;
X	     * "cprev" points to the revision, which must be NULL-terminated;
X	     */
X	    if (strcmp(tag, cp) == 0) {
X		if ((cp = index(cprev, ' ')) == NULL
X		    && (cp = index(cprev, ';')) == NULL
X		    && (cp = index(cprev, '\n')) == NULL)
X		    continue;
X		*cp = '\0';
X		(void) strcpy(rev, cprev);
X		return (-1);		/* look for branches off rev */
X	    } else {
X		while (!isspace(*cp))
X		    cp++;
X		cp++;
X	    }
X	}
X	return (1);
X    }
X    return (0);
X}
X
X/*
X * Decides if we should determine the highest branch number, and
X * returns it in "rev".  This is only done if there are
X * an even number of dots ('.') in the revision number passed in.
X */
Xstatic
Xget_branch(fp, rev)
X    FILE *fp;
X    char *rev;
X{
X    char line[MAXLINELEN];
X    char branch[50];
X    char *cp;
X    int len, dots = numdots(rev);
X
X    if ((dots & 1) != 0)
X	return;
X    (void) sprintf(branch, "%s.", rev);
X    len = strlen(branch);
X    while (fgets(line, sizeof(line), fp) != NULL) {
X	if (strncmp(line, RCSDESC, sizeof(RCSDESC) - 1) == 0)
X	    return;
X	if (isdigit(line[0])) {
X	    if ((cp = rindex(line, '\n')) != NULL)
X		*cp = '\0';		/* strip the newline */
X	    if (numdots(line) == dots+1 &&
X		strncmp(line, branch, len) == 0) {
X		if (strcmp(branch, line) <= 0)
X		    (void) strcpy(rev, line);
X	    }
X	}
X    }
X}
X
X/*
X * Look up a the most recent revision, based on the supplied date.
X * But do some funky stuff if necessary to follow any vendor branch.
X */
Xstatic
Xget_date(fp, date, rev, version)
X    FILE *fp;
X    char *date;
X    char *rev;
X    char *version;
X{
X    char *cp;
X
X    if ((numdots(rev) & 1) == 0) {
X	/*
X	 * A branch is the head, so get the revision from the branch
X	 * specified in "rev".  If we didn't get a match, try the trunk.
X	 */
X	get_branch_date(fp, date, rev, version);
X	if (version[0] == '\0') {
X	    if ((cp = index(rev, '.')) != NULL)
X		*cp = '\0';
X	    rewind(fp);
X	    get_branch_date(fp, date, rev, version);
X	}
X    } else {
X	/*
X	 * The trunk is the head.  Get the revision from the trunk and
X	 * see if it evaluates to 1.1.  If so, walk the 1.1.1 branch looking
X	 * for a match and return that; if not, just return 1.1.
X	 */
X	if ((cp = rindex(rev, '.')) != NULL)
X	    *cp = '\0';			/* turn the revision into a branch */
X	get_branch_date(fp, date, rev, version);
X	if (strcmp(version, "1.1") == 0) {
X	    rewind(fp);
X	    get_branch_date(fp, date, "1.1.1", version);
X	}
X    }
X}
X
Xstatic
Xget_branch_date(fp, date, rev, version)
X    FILE *fp;
X    char *date;
X    char *rev;
X    char *version;
X{
X    char line[MAXLINELEN], last_rev[50], curdate[50], date_dot[50];
X    int date_dots, date_dotlen;
X    char *cp, *semi;
X
X    last_rev[0] = '\0';
X    (void) strcpy(curdate, "00");	/* what happens at 2000 ad? */
X    (void) sprintf(date_dot, "%s.", rev);
X    date_dotlen = strlen(date_dot);
X    date_dots = numdots(rev);
X    while (fgets(line, sizeof(line), fp) != NULL) {
X	if (strncmp(line, RCSDESC, sizeof(RCSDESC) - 1) == 0)
X	    return;
X	if (isdigit(line[0])) {
X	    if ((cp = rindex(line, '\n')) != NULL)
X		*cp = '\0';		/* strip the newline */
X	    if ((date_dots == 0 || strncmp(date_dot, line, date_dotlen) == 0) &&
X		numdots(line) == date_dots+1)
X		(void) strcpy(last_rev, line);
X	    else
X		last_rev[0] = '\0';
X	    continue;
X	}
X	if (strncmp(line, RCSDATE, sizeof(RCSDATE) - 1) == 0 &&
X	    last_rev[0] != '\0') {
X	    for (cp = line; *cp && !isspace(*cp); cp++)
X		;
X	    while (*cp && isspace(*cp))
X		cp++;
X	    if (*cp && (semi = index(cp, ';')) != NULL) {
X		*semi = '\0';		/* strip the semicolon */
X		if (strcmp(cp, date) <= 0 && strcmp(cp, curdate) >= 0) {
X		    (void) strcpy(curdate, cp);
X		    (void) strcpy(version, last_rev);
X		}
X	    }
X	}
X    }
X}
END_OF_FILE
if test 10526 -ne `wc -c <'src/version_number.c'`; then
    echo shar: \"'src/version_number.c'\" unpacked with wrong size!
fi
# end of 'src/version_number.c'
fi
echo shar: End of archive 3 \(of 7\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 4 5 6 7 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 7 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.