[comp.sources.unix] v21i086: File distribution alternative to rdist, Part03/03

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

Submitted-by: Rich Salz <rsalz@bbn.com>
Posting-number: Volume 21, Issue 86
Archive-name: coda/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 3)."
# Contents:  COPYING client.c coda.1
# Wrapped by rsalz@litchi.bbn.com on Mon Apr  9 16:49:04 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'COPYING' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'COPYING'\"
else
echo shar: Extracting \"'COPYING'\" \(9934 characters\)
sed "s/^X//" >'COPYING' <<'END_OF_FILE'
X		    GNU GENERAL PUBLIC LICENSE
X		     Version 1, February 1989
X
X Copyright (C) 1989 Free Software Foundation, Inc.
X                    675 Mass Ave, Cambridge, MA 02139, USA
X Everyone is permitted to copy and distribute verbatim copies
X of this license document, but changing it is not allowed.
X
X			    Preamble
X
X  The license agreements of most software companies try to keep users
Xat the mercy of those companies.  By contrast, our General Public
XLicense is intended to guarantee your freedom to share and change free
Xsoftware--to make sure the software is free for all its users.  The
XGeneral Public License applies to the Free Software Foundation's
Xsoftware and to any other program whose authors commit to using it.
XYou can use it for your programs, too.
X
X  When we speak of free software, we are referring to freedom, not
Xprice.  Specifically, the General Public License is designed to make
Xsure that you have the freedom to give away or sell copies of free
Xsoftware, that you receive source code or can get it if you want it,
Xthat you can change the software or use pieces of it in new free
Xprograms; and that you know you can do these things.
X
X  To protect your rights, we need to make restrictions that forbid
Xanyone to deny you these rights or to ask you to surrender the rights.
XThese restrictions translate to certain responsibilities for you if you
Xdistribute copies of the software, or if you modify it.
X
X  For example, if you distribute copies of a such a program, whether
Xgratis or for a fee, you must give the recipients all the rights that
Xyou have.  You must make sure that they, too, receive or can get the
Xsource code.  And you must tell them their rights.
X
X  We protect your rights with two steps: (1) copyright the software, and
X(2) offer you this license which gives you legal permission to copy,
Xdistribute and/or modify the software.
X
X  Also, for each author's protection and ours, we want to make certain
Xthat everyone understands that there is no warranty for this free
Xsoftware.  If the software is modified by someone else and passed on, we
Xwant its recipients to know that what they have is not the original, so
Xthat any problems introduced by others will not reflect on the original
Xauthors' reputations.
X
X  The precise terms and conditions for copying, distribution and
Xmodification follow.
X
X		    GNU GENERAL PUBLIC LICENSE
X   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
X
X  0. This License Agreement applies to any program or other work which
Xcontains a notice placed by the copyright holder saying it may be
Xdistributed under the terms of this General Public License.  The
X"Program", below, refers to any such program or work, and a "work based
Xon the Program" means either the Program or any work containing the
XProgram or a portion of it, either verbatim or with modifications.  Each
Xlicensee is addressed as "you".
X
X  1. You may copy and distribute verbatim copies of the Program's source
Xcode as you receive it, in any medium, provided that you conspicuously and
Xappropriately publish on each copy an appropriate copyright notice and
Xdisclaimer of warranty; keep intact all the notices that refer to this
XGeneral Public License and to the absence of any warranty; and give any
Xother recipients of the Program a copy of this General Public License
Xalong with the Program.  You may charge a fee for the physical act of
Xtransferring a copy.
X
X  2. You may modify your copy or copies of the Program or any portion of
Xit, and copy and distribute such modifications under the terms of Paragraph
X1 above, provided that you also do the following:
X
X    a) cause the modified files to carry prominent notices stating that
X    you changed the files and the date of any change; and
X
X    b) cause the whole of any work that you distribute or publish, that
X    in whole or in part contains the Program or any part thereof, either
X    with or without modifications, to be licensed at no charge to all
X    third parties under the terms of this General Public License (except
X    that you may choose to grant warranty protection to some or all
X    third parties, at your option).
X
X    c) If the modified program normally reads commands interactively when
X    run, you must cause it, when started running for such interactive use
X    in the simplest and most usual way, to print or display an
X    announcement including an appropriate copyright notice and a notice
X    that there is no warranty (or else, saying that you provide a
X    warranty) and that users may redistribute the program under these
X    conditions, and telling the user how to view a copy of this General
X    Public License.
X
X    d) You may charge a fee for the physical act of transferring a
X    copy, and you may at your option offer warranty protection in
X    exchange for a fee.
X
XMere aggregation of another independent work with the Program (or its
Xderivative) on a volume of a storage or distribution medium does not bring
Xthe other work under the scope of these terms.
X
X  3. You may copy and distribute the Program (or a portion or derivative of
Xit, under Paragraph 2) in object code or executable form under the terms of
XParagraphs 1 and 2 above provided that you also do one of the following:
X
X    a) accompany it with the complete corresponding machine-readable
X    source code, which must be distributed under the terms of
X    Paragraphs 1 and 2 above; or,
X
X    b) accompany it with a written offer, valid for at least three
X    years, to give any third party free (except for a nominal charge
X    for the cost of distribution) a complete machine-readable copy of the
X    corresponding source code, to be distributed under the terms of
X    Paragraphs 1 and 2 above; or,
X
X    c) accompany it with the information you received as to where the
X    corresponding source code may be obtained.  (This alternative is
X    allowed only for noncommercial distribution and only if you
X    received the program in object code or executable form alone.)
X
XSource code for a work means the preferred form of the work for making
Xmodifications to it.  For an executable file, complete source code means
Xall the source code for all modules it contains; but, as a special
Xexception, it need not include source code for modules which are standard
Xlibraries that accompany the operating system on which the executable
Xfile runs, or for standard header files or definitions files that
Xaccompany that operating system.
X
X  4. You may not copy, modify, sublicense, distribute or transfer the
XProgram except as expressly provided under this General Public License.
XAny attempt otherwise to copy, modify, sublicense, distribute or transfer
Xthe Program is void, and will automatically terminate your rights to use
Xthe Program under this License.  However, parties who have received
Xcopies, or rights to use copies, from you under this General Public
XLicense will not have their licenses terminated so long as such parties
Xremain in full compliance.
X
X  5. By copying, distributing or modifying the Program (or any work based
Xon the Program) you indicate your acceptance of this license to do so,
Xand all its terms and conditions.
X
X  6. Each time you redistribute the Program (or any work based on the
XProgram), the recipient automatically receives a license from the original
Xlicensor to copy, distribute or modify the Program subject to these
Xterms and conditions.  You may not impose any further restrictions on the
Xrecipients' exercise of the rights granted herein.
X
X  7. The Free Software Foundation may publish revised and/or new versions
Xof the General Public License from time to time.  Such new versions will
Xbe similar in spirit to the present version, but may differ in detail to
Xaddress new problems or concerns.
X
XEach version is given a distinguishing version number.  If the Program
Xspecifies a version number of the license which applies to it and "any
Xlater version", you have the option of following the terms and conditions
Xeither of that version or of any later version published by the Free
XSoftware Foundation.  If the Program does not specify a version number of
Xthe license, you may choose any version ever published by the Free Software
XFoundation.
X
X  8. If you wish to incorporate parts of the Program into other free
Xprograms whose distribution conditions are different, write to the author
Xto ask for permission.  For software which is copyrighted by the Free
XSoftware Foundation, write to the Free Software Foundation; we sometimes
Xmake exceptions for this.  Our decision will be guided by the two goals
Xof preserving the free status of all derivatives of our free software and
Xof promoting the sharing and reuse of software generally.
X
X			    NO WARRANTY
X
X  9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
XFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
XOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
XPROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
XOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
XMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
XTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
XPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
XREPAIR OR CORRECTION.
X
X  10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
XWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
XREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
XINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
XOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
XTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
XYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
XPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
XPOSSIBILITY OF SUCH DAMAGES.
X
X		     END OF TERMS AND CONDITIONS
END_OF_FILE
if test 9934 -ne `wc -c <'COPYING'`; then
    echo shar: \"'COPYING'\" unpacked with wrong size!
fi
# end of 'COPYING'
fi
if test -f 'client.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'client.c'\"
else
echo shar: Extracting \"'client.c'\" \(15213 characters\)
sed "s/^X//" >'client.c' <<'END_OF_FILE'
X/*
X**  Copyright 1989 BBN Systems and Technologies Corporation.
X**  All Rights Reserved.
X**  This is free software, and may be distributed under the terms of the
X**  GNU Public License; see the file COPYING for more details.
X**
X**  Main code for CODA client.
X*/
X#define MAINLINE
X#include "client.h"
X#ifndef	VMS
X#include <sys/types.h>
X#include <sys/stat.h>
X#else
X#include <types.h>
X#include <stat.h>
X#endif	/* VMS */
X#ifdef	RCSID
Xstatic char RCS[] =
X	"$Header: client.c,v 2.0 90/04/09 15:34:23 rsalz Exp $";
X#endif	/* RCSID */
X
X
X/*
X**  A whole mess of global variables.
X*/
XSTATIC char	ACK[] = "ACK-";		/* Header if command was OK	*/
XSTATIC char	DAT[] = "DAT ITEM ";	/* Data file for from command	*/
XSTATIC char	NAK[] = "NAK-";		/* Header if it wasn't		*/
XSTATIC char	**Lines;		/* Lines read from server	*/
XSTATIC char	*Directory;		/* Where sources reside		*/
XSTATIC char	*Filename;		/* File for server to read	*/
XSTATIC char	*Root;			/* Remote root directory	*/
XSTATIC char	*ServerHost;		/* Host server is on		*/
XSTATIC char	*UserName;		/* Remote user name		*/
XSTATIC char	*UserPass;		/* Remote user password		*/
XSTATIC int	MaxLines;		/* Number of lines allocated	*/
XSTATIC BOOL	Noaction;		/* Just say what's outdated?	*/
XSTATIC int	NumLines;		/* Number of lines used		*/
XSTATIC int	Port = PORTNUMBER;	/* Port server is listening	*/
XSTATIC int	Verbose;		/* Tell what we're doing?	*/
XSTATIC BOOL	ChangeOwner;		/* Try to give away files?	*/
XSTATIC BOOL	SlowMode;		/* Do byte comparisons?		*/
XSTATIC BOOL	YoungerMode;		/* Can client have newer files?	*/
X
X
X
X/*
X**  Print an error message and exit.
X*/
Xvoid
XFatal(p)
X    char	*p;
X{
X    (void)fprintf(stderr, "Coda fatal error:\n\t%s\n", p);
X    exit(EXITERR);
X    /* NOTREACHED */
X}
X
X
X/*
X**  Get a line from the server; if it's a NAK, quit.
X*/
XSTATIC void
XQuitOnNack()
X{
X    char	buff[SIZE];
X
X    if (!SRVget(buff, sizeof buff))
X	Fatal("Server did not reply");
X    if (strncmp(buff, ACK, sizeof ACK - 1) == 0)
X	return;
X    if (strncmp(buff, NAK, sizeof NAK - 1) == 0)
X	Fatal(&buff[sizeof NAK - 1]);
X    (void)fprintf(stderr, "Bad from server:\n\t%s\n", buff);
X    Fatal("Protocol error");
X}
X
X
X/*
X**  Print a warning if user wants to see them.
X*/
XSTATIC void
XCant(Always, Action, Name)
X    BOOL	Always;
X    char	*Action;
X    char	*Name;
X{
X    char	buff[SIZE];
X    int		e;
X
X    if (Always || Verbose > 2) {
X	e = errno;
X	(void)sprintf(buff, "Can't %s %s", Action, Name);
X	errno = e;
X	perror(buff);
X    }
X}
X
X
X
X/*
X**  Set the mode, ownership, etc., on an item to be what they should be.
X*/
XSTATIC void
XSetStuff(Name, Mode, Uid, Gid, Time)
X    char	*Name;
X    int		Mode;
X    int		Uid;
X    int		Gid;
X    long	Time;
X{
X    struct stat	 Sb;
X#ifdef	ATTsysv
X    struct utime_t {
X	time_t	x;
X	time_t	y;
X    } t, *vec = &t;
X#else
X    time_t	 vec[2];
X#endif	/* ATTsysv */
X
X    /* Get current status. */
X    if (stat(Name, &Sb) < 0) {
X	Cant(TRUE, "stat", Name);
X	return;
X    }
X
X    /* Fix up the permissions on files. */
X    if ((Sb.st_mode & 0777) != Mode) {
X	if (chmod(Name, Mode) < 0)
X	    Cant(FALSE, "chmod", Name);
X	else if (Verbose > 1)
X	    (void)printf("chmod 0%o %s\n", Mode, Name);
X    }
X
X    /* Fix up the modication time. */
X    if (Sb.st_mtime != Time) {
X#ifdef	ATTsysv
X	t.x = t.y = Time;
X#else
X	vec[0] = vec[1] = Time;
X#endif	/* ATTsysv */
X	if (utime(Name, vec) < 0)
X	    Cant(FALSE, "set time", Name);
X	else if (Verbose > 1)
X	    (void)printf("settime %ld %s\n", (long)Time, Name);
X    }
X
X    /* Fix up the owner. */
X    if (ChangeOwner && (Sb.st_uid != Uid || Sb.st_gid != Gid)) {
X	if (chown(Name, Uid, Gid) < 0)
X	    Cant(FALSE, "chown", Name);
X	else if (Verbose > 1)
X	    (void)printf("chown %d chgrp %d %s\n", Uid, Gid, Name);
X    }
X}
X
X
X/*
X**  Do what's necessary to create a directory.
X*/
XSTATIC void
XDoDir(Name, Uid, Gid, Mode, Time)
X    char	*Name;
X    int		Uid;
X    int		Gid;
X    int		Mode;
X    long	Time;
X{
X    struct stat	Sb;
X#ifdef	VMS
X    char	buff[SIZE];
X#endif	/* VMS */
X
X#ifdef	VMS
X    (void)sprintf(buff, "%s.dir", Name);
X    Name = buff;
X#endif	/* VMS */
X
X    /* If directory doesn't exist, create it. */
X    if (stat(Name, &Sb) < 0) {
X	if (Verbose > 0 || Noaction) {
X	    (void)printf("mkdir %s\n", Name);
X	    if (Noaction)
X		return;
X	}
X	if (mkdir(Name, Mode) < 0)
X	    Cant(TRUE, "mkdir", Name);
X    }
X
X    /* Set the other bits. */
X    if (!Noaction)
X	SetStuff(Name, Mode, Uid, Gid, Time);
X}
X
X
X
X/*
X**  Compare two files, if they're different, move the second onto
X**  the first.
X*/
XSTATIC void
XGetFile(Name, Size, KnowItsWrong)
X    char		*Name;
X    REGISTER long	Size;
X    BOOL		KnowItsWrong;
X{
X    REGISTER FILE	*F;
X    REGISTER FILE	*Old;
X    REGISTER char	*p;
X    REGISTER int	c;
X    REGISTER int 	c2;
X    char		buff[SIZE];
X    char		temp[SIZE];
X
X    if (!KnowItsWrong && Verbose > 4)
X	(void)printf("Checking %s\n", Name);
X
X    /* Make a temporary filename in the same directory. */
X    (void)strcpy(temp, Name);
X    if (p = strrchr(temp, '/'))
X	p++;
X    else
X	p = temp;
X    (void)strcpy(p, "codaXXXXXX");
X    (void)mktemp(p);
X
X    if ((F = fopen(temp, "w")) == NULL) {
X	Cant(TRUE, "fopen", temp);
X	return;
X    }
X
X    /* Tell the server to send us the file. */
X    (void)sprintf(buff, "SEND %s\n", Name);
X    SRVput(buff);
X    if (!SRVget(buff, sizeof buff)
X     || strncmp(buff, NAK, sizeof NAK - 1) == 0) {
X	Cant(TRUE, "start to read", temp);
X	(void)fclose(F);
X	(void)unlink(temp);
X	return;
X    }
X
X    /* Read it from the server. */
X    while (--Size >= 0) {
X	c = SRVcget();
X	(void)putc(c, F);
X    }
X    (void)fclose(F);
X    QuitOnNack();
X
X    /* If we know it's wrong, just move it. */
X    if (KnowItsWrong) {
X	(void)unlink(Name);
X	if (rename(temp, Name) < 0)
X	    Cant(TRUE, "rename to", Name);
X	(void)sprintf(buff, "MESG took %s\n", Name);
X	SRVput(buff);
X	QuitOnNack();
X	return;
X    }
X
X    /* Open both old and new files. */
X    if ((F = fopen(temp, "r")) == NULL) {
X	Cant(TRUE, "reopen", temp);
X	return;
X    }
X    if ((Old = fopen(Name, "r")) == NULL) {
X	Cant(TRUE, "fopen", Name);
X	(void)fclose(F);
X	return;
X    }
X
X    /* Do byte-for-byte comparisons. */
X    while ((c = getc(F)) == (c2 = getc(Old)))
X	if (c == EOF)
X	    break;
X    (void)fclose(F);
X    (void)fclose(Old);
X
X    /* Different? */
X    if (c != c2) {
X	if (Verbose || Noaction) {
X	    (void)printf("Update %s\n", Name);
X	    if (Noaction) {
X		(void)unlink(temp);
X		return;
X	    }
X	}
X	(void)unlink(Name);
X	if (rename(temp, Name) < 0)
X	    Cant(TRUE, "rename to", Name);
X	(void)sprintf(buff, "MESG took %s\n", Name);
X    }
X    else
X	(void)sprintf(buff, "MESG ignore %s\n", Name);
X    SRVput(buff);
X    QuitOnNack();
X    (void)unlink(temp);
X}
X
X
X/*
X**  Do what's necessary to create a file.
X*/
XSTATIC void
XDoFile(Name, Uid, Gid, Mode, Time, Size)
X    char		*Name;
X    int			Uid;
X    int			Gid;
X    int			Mode;
X    long		Time;
X    long		Size;
X{
X    struct stat		Sb;
X
X    if (stat(Name, &Sb) < 0 || Sb.st_size != Size) {
X	/* File doesn't exist or size is wrong; get it. */
X	if (YoungerMode && Sb.st_mtime > Time) {
X	    (void)printf("Younger %s\n", Name);
X	    return;
X	}
X	if (Verbose || Noaction) {
X	    (void)printf("Update %s\n", Name);
X	    if (Noaction)
X		return;
X	}
X	GetFile(Name, Size, TRUE);
X    }
X    else {
X	if (YoungerMode && Sb.st_mtime > Time) {
X	    (void)printf("Younger %s\n", Name);
X	    return;
X	}
X	if (SlowMode || Sb.st_mtime != Time)
X	    /* Slow mode or the times are different; get and check. */
X	    GetFile(Name, Size, FALSE);
X    }
X
X    /* Set the other bits. */
X    if (!Noaction)
X	SetStuff(Name, Mode, Uid, Gid, Time);
X}
X
X
X/*
X**  Read the list of files from the server, than parse them.
X*/
XSTATIC void
XParseListResult()
X{
X    REGISTER char	**L;
X    REGISTER char	**Lend;
X    REGISTER char	*p;
X    char		buff[SIZE];
X    char		*Name;
X    int			Uid;
X    int			Gid;
X    long		Size;
X    long		Time;
X    int			Mode;
X    int			What;
X
X    /* Read lines from server. */
X    for (NumLines = 0; SRVget(buff, sizeof buff); ) {
X	/* Check for end of list or a bad line. */
X	if (strncmp(buff, NAK, sizeof NAK - 1) == 0) {
X	    (void)printf("%s\n", &buff[sizeof NAK - 1]);
X	    return;
X	}
X	if (strncmp(buff, ACK, sizeof ACK - 1) == 0) {
X	    (void)printf("%s\n", &buff[sizeof ACK - 1]);
X	    break;
X	}
X	if (strncmp(buff, DAT, sizeof DAT - 1)) {
X	    (void)fprintf(stderr, "Bogus line from the server:\n\t%s\n", buff);
X	    continue;
X	}
X
X	/* Need more room for this line? */
X	if (NumLines == MaxLines - 1) {
X	    MaxLines += LINES_DELTA;
X	    Lines = GROW(Lines, char*, MaxLines);
X	}
X	Lines[NumLines++] = COPY(&buff[sizeof DAT - 1]);
X    }
X
X    /* Now loop over what we got and parse it. */
X    for (L = Lines, Lend = &Lines[NumLines]; L < Lend; L++) {
X	/* Set defaults to show the stuff is bad. */
X	Name = NULL;
X	Uid = -1;
X	Gid = -1;
X	Size = -1;
X	Time = -1;
X	Mode = -1;
X	What = -1;
X	for (p = strcpy(buff, *L); *p; ) {
X	    switch (*p) {
X	    default:
X		(void)fprintf(stderr, "Bad field from server:\n\t%s\n", *L);
X		break;
X	    case 'N':
X		Name = &p[2];
X		break;
X	    case 'W':
X		if (p[2] == 'd' || p[2] == 'f')
X		    What = p[2];
X		break;
X	    case 'U':
X		Uid = atoi(&p[2]);
X		break;
X	    case 'G':
X		Gid = atoi(&p[2]);
X		break;
X	    case 'M':
X		Mode = atoi(&p[2]);
X		break;
X	    case 'S':
X		Size = atol(&p[2]);
X		break;
X	    case 'T':
X		Time = atol(&p[2]);
X		break;
X	    }
X
X	    /* Move to next field. */
X	    while (*p && !WHITE(*p))
X		p++;
X	    if (*p)
X		for (*p++ = '\0'; *p && WHITE(*p); )
X		    p++;
X	}
X
X	/* Got everything? */
X	if (What < 0 || Mode < 0 || Gid < 0 || Size < 0 || Time < 0
X#ifdef	NOBODY
X	 || (Uid < 0 && Uid != NOBODY)
X#else
X	 || Uid < 0
X#endif	/* NOBODY */
X	 || Name == NULL)
X	    (void)fprintf(stderr, "Badly formed line:\n\t%s\n", *L);
X	else if (What == 'd')
X	    DoDir(Name, Uid, Gid, Mode, Time);
X	else
X	    DoFile(Name, Uid, Gid, Mode, Time, Size);
X    }
X}
X
X
X
X/*
X**  Scan a word for yes or no.
X*/
XSTATIC int
XGetYesOrNo(p, where, flag)
X    char	*p;
X    char	*where;
X    char	flag;
X{
X    char	buff[SIZE];
X
X    switch (*p++) {
X    case 'Y': case 'y':
X	if (p[0] == '\0')
X	    return TRUE;
X	if ((p[1] == 'E' || p[1] == 'e')
X	 && (p[2] == 'S' || p[2] == 's')
X	 && p[3] == '\0')
X	break;
X    case 'N': case 'n':
X	if (p[0] == '\0')
X	    return TRUE;
X	if ((p[1] == 'O' || p[1] == 'o')
X	 && p[2] == '\0')
X	    return TRUE;
X	break;
X    }
X    (void)sprintf(buff, "Expecting one of [YyNn] %s for '%c' flag", where, flag);
X    Fatal(buff);
X    /* NOTREACHED */
X}
X
X
X/*
X**  Skip past the current word and any whitespace that follows it.
X*/
XSTATIC char *
XSkip(p)
X    char	*p;
X{
X    while (*p && !WHITE(*p))
X	p++;
X    while (*p && WHITE(*p))
X	p++;
X    return p;
X}
X
X
X/*
X**  Read the init file.
X*/
XSTATIC void
XReadInitFile()
X{
X    static char	Where[] = "in init file";
X    FILE	*F;
X    char	*p;
X    char	buff[SIZE];
X
X    /* Get the filename, open the file. */
X    if ((p = getenv("CODA")) == NULL || (F = fopen(p, "r")) == NULL) {
X#ifdef	VMS
X	if ((F = fopen(CODA_INIT, "r")) == NULL)
X	    return;
X#else
X	if ((p = getenv("HOME")) == NULL)
X	    return;
X	(void)sprintf(buff, CODA_INIT, p);
X	if ((F = fopen(buff, "r")) == NULL)
X	    return;
X#endif	/* VMS */
X    }
X
X    while (fgets(buff, sizeof buff, F)) {
X	/* Skip blank and comment lines. */
X	if (p = strchr(buff, '\n'))
X	    *p = '\0';
X	if (buff[0] == '\0' || buff[0] == '#')
X	    continue;
X
X	if (strncmp(buff, "dir", 3) == 0) {
X	    p = Skip(buff);
X	    Directory = COPY(p);
X	}
X	else if (strncmp(buff, "file", 4) == 0) {
X	    p = Skip(buff);
X	    Filename = COPY(p);
X	}
X	else if (strncmp(buff, "host", 4) == 0) {
X	    p = Skip(buff);
X	    ServerHost = COPY(p);
X	}
X	else if (strncmp(buff, "owner", 5) == 0) {
X	    p = Skip(buff);
X	    ChangeOwner = GetYesOrNo(p, Where, 'o');
X	}
X	else if (strncmp(buff, "pass", 4) == 0) {
X	    p = Skip(buff);
X	    UserPass = COPY(p);
X	}
X	else if (strncmp(buff, "port", 4) == 0) {
X	    p = Skip(buff);
X	    Port = atoi(p);
X	}
X	else if (strncmp(buff, "root", 4) == 0) {
X	    p = Skip(buff);
X	    Root = COPY(p);
X	}
X	else if (strncmp(buff, "slow", 4) == 0) {
X	    p = Skip(buff);
X	    SlowMode = GetYesOrNo(p, Where, 's');
X	}
X	else if (strncmp(buff, "user", 4) == 0) {
X	    p = Skip(buff);
X	    UserName = COPY(p);
X	}
X	else if (strncmp(buff, "verb", 4) == 0) {
X	    p = Skip(buff);
X	    Verbose = atoi(p);
X	}
X	else if (strncmp(buff, "young", 5) == 0) {
X	    p = Skip(buff);
X	    YoungerMode = GetYesOrNo(p, Where, 'y');
X	}
X	else {
X	    (void)fprintf(stderr, "Unknown line:\n\t%s\n", buff);
X	    Fatal("Bad init file");
X	}
X    }
X    (void)fclose(F);
X}
X
X
Xmain(ac, av)
X    int		ac;
X    char	*av[];
X{
X    static char	Where[] = "on command line";
X    char	buff[SIZE];
X    char	pass[SIZE];
X    int		i;
X
X    /* Get defaults from environment if possible. */
X    ReadInitFile();
X    (void)umask(0);
X
X    /* Parse JCL. */
X    while ((i = getopt(ac, av, "cd:f:h:no:p:r:s:tu:v:x:y:")) != EOF)
X	switch (i) {
X	default:
X	    Fatal("Bad calling sequence");
X	    /* NOTREACHED */
X	case 'c':
X	    Copyright();
X	    exit(EXITOK);
X	    /* NOTREACHED */
X	case 'd':
X	    Directory = optarg;
X	    break;
X	case 'f':
X	    Filename = optarg;
X	    break;
X	case 'h':
X	    ServerHost = optarg;
X	    break;
X	case 'n':
X	    Noaction = TRUE;
X	    break;
X	case 'o':
X	    ChangeOwner = GetYesOrNo(optarg, Where, 'o');
X	    break;
X	case 'p':
X	    Port = atoi(optarg);
X	    break;
X	case 'r':
X	    Root = optarg;
X	    break;
X	case 's':
X	    SlowMode = GetYesOrNo(optarg, Where, 's');
X	    break;
X	case 't':
X	    SRVtrace = TRUE;
X	    break;
X	case 'u':
X	    UserName = optarg;
X	    break;
X	case 'v':
X	    Verbose = atoi(optarg);
X	    break;
X	case 'x':
X	    UserPass = optarg;
X	    break;
X	case 'y':
X	    YoungerMode = GetYesOrNo(optarg, Where, 'y');
X	    break;
X	}
X    av += optind;
X
X    /* Got everything we need? */
X    if (ServerHost == NULL)
X	Fatal("Need name of server host");
X    if (Port == 0)
X	Fatal("Need port to connect to");
X    if (UserName == NULL)
X	Fatal("Need your name on the server machine");
X
X    /* Get passwword. */
X    if (UserPass == NULL) {
X	(void)printf("Enter password:");
X	(void)fflush(stdout);
X	GetPassword(pass, sizeof pass);
X	UserPass = pass;
X    }
X
X    /* Talk to the server */
X    if (!SRVopen(ServerHost, Port))
X	Fatal("Can't open connection to server");
X    QuitOnNack();
X
X    /* Log in. */
X    (void)sprintf(buff, "USER %s %s", UserName, UserPass);
X    SRVput(buff);
X    QuitOnNack();
X
X    /* Tell the server where to go. */
X    if (Directory) {
X	(void)sprintf(buff, "GOTO %s", Directory);
X	SRVput(buff);
X	QuitOnNack();
X    }
X
X    /* Read the file. */
X    if (Filename) {
X	(void)sprintf(buff, "READ %s", Filename);
X	SRVput(buff);
X    }
X    else
X	SRVput("READ");
X    QuitOnNack();
X
X    /* Set the root directory. */
X    if (Root) {
X	(void)sprintf(buff, "ROOT %s", Root);
X	SRVput(buff);
X	QuitOnNack();
X    }
X
X    /* Any other arguments are block names. */
X    if (*av) {
X	/* Get some space. */
X	MaxLines = LINES_DELTA;
X	Lines = NEW(char*, MaxLines);
X	for ( ; *av; av++) {
X	    (void)printf("Doing %s...\n", *av);
X	    (void)sprintf(buff, "LIST %s", *av);
X	    SRVput(buff);
X	    ParseListResult();
X	}
X    }
X    else {
X	/* No arguments means do the whole shebang. */
X	MaxLines = LINES_DELTA * 2;
X	Lines = NEW(char*, MaxLines);
X	(void)printf("Working...\n");
X	SRVput("LIST");
X	ParseListResult();
X    }
X
X    /* That's all she wrote. */
X    SRVclose();
X    exit(EXITOK);
X    /* NOTREACHED */
X}
END_OF_FILE
if test 15213 -ne `wc -c <'client.c'`; then
    echo shar: \"'client.c'\" unpacked with wrong size!
fi
# end of 'client.c'
fi
if test -f 'coda.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'coda.1'\"
else
echo shar: Extracting \"'coda.1'\" \(14762 characters\)
sed "s/^X//" >'coda.1' <<'END_OF_FILE'
X.TH CODA 1 LOCAL
X.SH NAME
Xcoda \- Code distribution aid
X.SH SYNOPSIS
X.RS
X.ti -.5i
X.B coda
X.na
X[
X.B \-c
X] [
X.BI \-d dir
X] [
X.BI \-r dir
X] [
X.BI \-h host
X] [
X.BI \-p port#
X] [
X.BI \-f file
X] [
X.BI \-u name
X] [
X.BI \-x pass
X] [
X.BI \-s y_or_n
X] [
X.BI \-y y_or_n
X] [
X.BI \-o y_or_n
X] [
X.B \-n
X] [
X.B \-t
X] [
X.B \-v #
X] [
X.I names...
X]
X.ad
X.RE
X.SH DESCRIPTION
X.I Coda
Xis used to keep source distributions current across a set of machines.
XIt is not as flexible as
X.IR rdist (1),
Xbut it requires less of the client machines because they do not have to be
Xrunning Unix or the Berkeley networking programs.
XThe other difference is that when using
X.IR rdist ,
Xthe server ``pushes'' files out to the clients, while with
X.I coda
Xthe clients ``pull'' files from the server as they need them.
X.I Coda
Xwas designed to have a simple client that easily ports to a wide
Xvariety of systems.
X.PP
XThe most common way of using
X.I coda
Xis to create a configuration file in your home directory.
XOn
X.SM UNIX
Xsystems, the file is named
X.I $HOME/.codarc
Xwhile on
X.SM VMS
Xmachines it is named
X.IR SYS$LOGIN:CODA.DAT .
XOn either system, to use a different file, set the environment variable
X.I CODA
Xto the name of the file.
XThis is useful to share a common a file among project members.
XThe config file identifies where the server host is, your name
X(and password) on the server, and where the Codafile and sources
Xare to be found.
X.PP
XThe config file is a set of lines that make name-value pairs.
XLines starting with a pound sign are ignored.
XThe following is a sample configuration file; the field names are
Xexplained below:
X.RS
X.nf
X.ta \w'#verbose  'u
X##  Server host and port number
Xhost	papaya.bbn.com
Xport	1999
X##  Our name and password on the server host
Xuser	rsalz
Xpass	doubtful
X##  Directory to start from, name of the file to read
Xdir	/usr/cronus
Xfile	src/make_all/Codafile
X##
X#root	/nfs/papaya/u1/cronusbuild/cronus
Xslow	n
Xverbose	1
X##  Can client have newer files?  Yes, while porting to it
Xyounger	y
X.fi
X.RE
X.PP
XIn addition to using the config file, it is possible to specify the
Xparameters by using command line flags.
XAny values set by flags override the values in the config file.
XThe following tables matches names in the config file to command line flags.
X.RS
X.nf
X.ta \w'younger  'u +\w'Flag    'u
XFile	Flag	Meaning
X\l'5i'
Xhost	\-h	Name of the server host
Xport	\-p	Port to connect to; default is 1999
Xuser	\-u	Your login name on the server
Xpass	\-p	Your password on the server; see below
Xdir	\-d	Directory for server to change to
Xfile	\-f	File to read (if not Codafile) in the directory given above
X	\-n	No action, just report what would be changed
Xroot	\-r	Path to find files in; overrides what the Codafile declared
Xslow	\-s	Do byte-for-byte comparisons
X	\-t	Trace the protocol
Xverbose	\-v	How much information to show
Xyounger	\-y	Allow newer files to be on the client
Xowner	\-o	Retain original ownership
X.fi
X.RE
X.PP
XIf your password is not listed in the config file, or specified on the
Xcommand line,
X.I coda
Xwill ask you for it.
XIf you do put your password in the config file, make sure that nobody
Xelse can read the file!
X.PP
XOnce the file has been read, the server will send over a list of
Xfiles and directories.
X.I Coda
Xwill parse this list, and update any files and directories it has to.
X.PP
XFor efficiency,
X.I coda
Xnormally just checks the sizes of the local and master copy, and will only
Xupdate the local copy if they are different.
XIf the modification dates are different,
X.I coda
Xwill get a copy of the file and do a byte-for-byte comparison to see if
Xthe files actually are different.
XIf the ``\-s'' flag is used,
X.I coda
Xwill ignore the time check, and will get every file
Xand compare it with the local copy.
X.PP
XIf you use the ``\-n'' flag,
X.I coda
Xwill not do work, but will instead just report what files
Xand directories are out of date.
X.PP
XDuring a port, a client will often have files that have been modified after
Xthey have been obtained from the server, but before the changes have been
Xintegrated into the master sources.
XOn systems where the client's timestamp can be trusted (e.g., not on VMS
Xbecause there is no equivalent of the
X.IR utime (2)
Xcall), the ``\-y'' flag may be used to specify that the client might
Xhave files that are younger, or more recent, than those on the server.
XThe default option, ``\-yn'' will cause
X.I coda
Xto check if the files are different, and overwrite them, if the timestamps
Xare different in any way.
X.PP
XBy default the person who runs
X.I coda
Xwill be the owner of the files.
XTo make the ownerships match those on the server, use the ``\-oy'' flag.
XThis is probably more correct in a production environment than when
Xporting.
X.PP
XIf you use the ``\-v'' flag,
X.I coda
Xwill give more verbose information about what it is doing.
XFor example, ``\-v1'' will list all files and directories that have
Xto be created.
XIf you use the ``\-v2'' flag,
X.I coda
Xwill report when it has to change the ownership or permission on files,
Xsuch as when a file has the right time and size, but is owned by the
Xwrong person.
XSince it is normally not a big problem if the wrong person owns a file,
Xor if it has the wrong permissions,
X.I coda
Xdoes not complain if something cannot be fixed.
XYou can use the ``\-v3'' flag to see a message whenever the program
Xcannot set the file mode or ownership.
XIf you use the ``\-v4'' flag
X.I coda
Xwill report on every file as it does the byte-for-byte comparison of slow mode.
XYou should probably always use either ``\-v1'' or ``\-v3.''
X.PP
XTo trace all interactions with the server, use the ``\-t'' flag; this
Xwill provide output like the following:
X.RS
X.nf
X\fIprompt%\fP coda -d/usr/rsalz -f coda/Codafile -h papaya -u rsalz -x ... -t misc
X<<<ACK-Hello FIG.BBN.COM; how are you today?
X>>>USER rsalz ...
X<<<ACK-Logged in rsalz
X>>>GOTO /usr/rsalz
X<<<ACK-Done
X>>>READ coda/Codafile
X<<<ACK-Done
XDoing misc...
X>>>LIST misc
X<<<DAT ITEM W=d N=cbin U=-2 G=121 M=511 S=512 T=609796129
X<<<DAT ITEM W=f N=cbin/dcc U=-2 G=121 M=493 S=150 T=603474106
X	...
X<<<ACK-Found 105 files
XFound 105 files
Xmkdir cbin
XUpdate cbin/dcc
X	...
X>>>QUIT
X\fIprompt%\fP
X.fi
X.RE
X(Lines without the angle brackets are the
X.IR coda 's
Xnormal output.)
XThis is detailed information and generally nobody but your local
X.I coda
Xmaintainer will care.
X.PP
XThe ``\-c'' flag prints out copyright and version information.
X.SH "CODAFILES"
XThe coda server reads a file to determine what parts of a distribution
Xget sent to which hosts.
XThe file is normally named
X.IR Codafile .
XThis section explains how to write codafiles, and does not have to
Xbe read by everyone.
X.PP
XBlank lines are ignored, as is anything after a pound sign.
XWhitespace is not significant.
XThere are a few keywords, and they appear in bold in the examples
Xbelow.
XTo use a keyword as a filename (e.g., if you have a directory named
X.IR class ),
Xput it in quotes.
XIt is also necessary to quote words if they contain any characters
Xother than letters, numbers, or the characters period, slash, or underscore.
X.PP
XThere are four constructs in a Codafile:  parameter definitions,
Xhost definitions, class definitions, and file definitions or blocks.
XThey may appear in any order.
X.PP
XParameter definitions are a catch-all for miscellaneous parameters.
XThere are currently three parameters.
XMost pathnames in a Codafile will be relative paths, the ``root''
Xis used for any directories or files that do not start with a slash.
XFor example:
X.RS
X\fBdefine\fP \fBroot\fP \fB=\fP /usr/cronus \fB;\fP
X.RE
XThis is most useful when the Codafile for a system is not at
Xthe top of the directory tree, or when it exists in someone's home
Xdirectory.
X.PP
XAt times it is useful to use the same Codafile for several different
Xsource trees.
XThe easiest way to do this is to not allow the clients to specify the
Xdifferent roots.
XSince this can be a security hole, a Codafile can specify that 
Xthe root directory specified in the file may not be overridden by the
Xclient.
XTo do this, use the following line:
X.RS
X\fBdefine\fP \fBrooted\fP \fB=\fP yes \fB;\fP
X.RE
XThe value can be any word starting with the letter
X.IR y .
XThe default value is \fIno\fP
X.PP
XThe final parameter controls whether or not binary files are sent.
XWhen preparing a list of files, the server can read the first few bytes
Xto see if a file is an ``a.out'' file, and if so, it will not list it.
XTo suppress this check, add a line like this:
X.RS
X\fBdefine\fP \fBbinaries\fP \fB=\fP yes \fB;\fP
X.RE
XThe value can be any word starting with the letter
X.IR y .
XThe default value is \fIyes\fP
X.PP
XHost definitions are used to name the hosts that are allowed to
Xreceive the distribution, and to identify the system type.
XBoth the names and types are arbitrary text strings; host names
Xare converted to all uppercase.
XA client's hostname must be defined in the Codafile before the server
Xwill send any files to it.
X.RS
X.nf
X.ta \w'host  'u +\w'pineapple.bbn.com  'u +\w'=  'u
X\fBhost\fP	citron.bbn.com	\fB=\fP VMS \fB;\fP
X\fBhost\fP	pineapple.bbn.com	\fB=\fP ultrix \fB;\fP
X\fBhost\fP	doe.bbn.com	\fB=\fP sun4 \fB;\fP
X\fBhost\fP	papaya.bbn.com	\fB=\fP sun3 \fB;\fP
X.fi
X.RE
XIf for some reason the server cannot determine the client's host name,
Xthen the name
X.I _ANYHOST
Xis used.
X.PP
XIn order to make the Codafile shorter, hosts are grouped into classes.
XFor example, it is convenient to be able to say ``give all the Unix
Xmachines the files in foo.''
XClasses are defined in two ways, by being named as a system type in a
Xhost definition, like above, or by appearing in a class definition,
Xlike this:
X.RS
X.nf
X.DT
X\fBclass\fP	SUN	\fB=\fP sun3, sun4 \fB;\fP
X\fBclass\fP	UNIX	\fB=\fP SUN, ultrix \fB;\fP
X.fi
X.RE
XNote that classes can be nested and can contain other classes.
XIn particular, it is possible to make loops that will tie the server
Xup in knots.
XUnlike host names, case is significant in class names.
XBy convention, classes defined as host types are in lowercase, while
Xthose defined as class types are in uppercase.
XThe special built-in class
X.I _ALL
Xcontains all hosts except
X.IR _ANYHOST .
XA Codafile for a public archive might start off like this:
X.RS
X.nf
X.ta \w'define  'u +\w'_ANYHOST   'u
X\fBdefine\fP	\fBroot\fP	\fB=\fP /usr/spool/ftp \fB;\fP
X\fBdefine\fP	\fBbinaries\fP	\fB=\fP no \fB;\fP
X\fBhost\fP	_ANYHOST	\fB=\fP guest \fB;\fP
X\fBclass\fP	WORLD	\fB=\fP _ALL, _ANYHOST \fB;\fP
X.fi
X.RE
X.PP
XThe most important part of a Codafile is the file definitions, or blocks.
XThese have the following syntax (optional items are shown in square brackets):
X.RS
X.nf
X.DT
X[ \fBonly_explicitly\fP ] name \fB:\fP class \fB{\fP
X	item
X		[ \fBexcept\fP [\fBdirectory\fP] class pattern, ... ; ]
X		[ \fBexcept\fP [\fBdirectory\fP] class pattern, ... ; ]
X	item
X		...
X	\fBdirectory\fP item
X		[ \fBexcept\fP [\fBdirectory\fP] class pattern, ... ; ]
X\fB}\fP
X.fi
X.RE
XThe name is used by client programs to limit the set of files they
Xwant to receive, while the class is used by the server to determine
Xif the client has any access to the block at all.
XIf a client does not name specific blocks, then all blocks in the file are
Xexamined.
XIf a block starts with the \fIonly_explicitly\fP keyword, than it must
Xbe explicitly named by the client, otherwise it is not listed.
XThis is useful, for example, when the default is to distribute a complete
Xsystem without test data.
X.PP
XWithin the block, each item names either a file or a directory.
XIf the item does not start with a slash, then the root is prepended.
XIf the item is a directory, than the server will walk down all the
Xfiles and directories beneath the named item.
XTo name just a directory, put the word \fIdirectory\fP before the
Xitem name.
XFor example:
X.RS
X.nf
Xheaders \fB:\fP _ALL \fB{\fP
X	include
X	\fBdirectory\fP cetc
X	cetc/hostfile
X	cetc/typefile
X	/etc/motd
X\fB}\fP
X.fi
X.RE
XThis specifies that all hosts get the directory named
X.I include
Xand all files and directories within it.
XThe next three lines specify that all clients should have the
X.I cetc
Xdirectory and the two files
X.I hostfile
Xand
X.I typefile
Xwithin it.
XThe actual location of these files on the server depends on the value
Xof the root parameter.
XTheir location on the client is relative to the directory from which the
X.I coda
Xwas first invoked.
XThe last line specifies that the motd file
Xshould exist with the indicated full pathname, regardless of the value
Xof the root parameter, or where the client is being run from.
X.PP
XItems can be given exception lists.
XException lists are patterns similar to shell wildcards:
X.RS
X.nf
X.ta \w'Character  'u
XCharacter	Meaning
X?	Any single character
X*	Zero or more characters
X[abc..09]	Any single character inside the brackets
X	The sequence ``a-z'' can be used to mean ``abcdefghijklmnopqrstuvwxyz''
X[^a-z]	Any single character not inside the brackets.
X\ex	The character ``x'' has no special meaning (e.g., \e?)
X.fi
X.RE
XException patterns only match against the last part of the pathname.
XThat is,
X.I mgr.c
Xand not
X.IR /usr/cronus/src/lib/asynclib/mgr.a .
XTo match against the whole pathname, precede the pattern with an uparrow.
XThis can be necessary when you want to exclude a particular file that
Xhas the same part of name as a directory further up in the source tree.
X.PP
XFor example, with the following hierarchy:
X.RS
X.nf
Xclib/commandfile
Xclib/cronus.a
Xclib/diamond/fonts/helvetica10.font
Xclib/diamond/fonts/tv8b.font
Xclib/diamond/lib.a
Xclib/diamond/config
Xclib/editkeysfile
Xclib/lint/llib-lasync
Xclib/lint/llib-lasync.ln
Xclib/lint/llib-lulib
Xclib/lint/llib-lulib.ln
Xclib/malloctool.a
X.fi
X.RE
XAnd the following block definition:
X.RS
X.nf
Xlibrary \fB:\fP _ALL {
X    clib
X	\fBexcept\fP _ALL "*.a", "*.ln" \fB;\fP
X	\fBexcept\fP \fBdirectory\fP VMS diamond, lint \fB;\fP
X}
X.fi
X.RE
XThen, no clients will get the compiled libraries or
X.IR lint (1)
Xlibraries,
Xand VMS clients will not get any files in the lint or diamond directories.
XAll clients will get the (presumably text) files named
X.IR commandfile ,
Xand
X.IR editkeysfile .
X.PP
XWith a good set of classes it is quite easy to tersely specify distributions.
XThe following example suffices to distribute the major part of the
XCronus source tree:
X.RS
X.nf
Xsources \fB:\fP _ALL \fB{\fP
X	src
X		\fBexcept\fP	VMS	Makefile, "*.sh", "*.s", "llib-l*" \fB;\fP
X		\fBexcept\fP	UNIX	descrip.mms,  "*.opt", "*.com", "*.mar" \fB;\fP
X		\fBexcept\fP	_ALL	"*.[oa]", "*.ln" \fB;\fP
X		\fBexcept\fP	_ALL	a.out, core, foo, tags \fB;\fP
X\fB}\fP
X.fi
X.RE
X.SH AUTHOR
XThis program was written by Rich $alz <rsalz@bbn.com>.
XIt has the following copyright:
X.RS
X.nf
XCopyright 1989 BBN Systems and Technologies Corporation.
XAll Rights Reserved.
XThis is free software, and may be distributed under the terms of the
XGNU Public License; see the file COPYING for more details.
X
X$Header: coda.1,v 2.0 90/03/23 15:01:50 rsalz Exp $
X.fi
X.RE
X.SH "SEE ALSO"
Xcodaserver(8).
END_OF_FILE
if test 14762 -ne `wc -c <'coda.1'`; then
    echo shar: \"'coda.1'\" unpacked with wrong size!
fi
# end of 'coda.1'
fi
echo shar: End of archive 3 \(of 3\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 3 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.