OpenBSD-4.6/usr.bin/sup/src/supcmeat.c

Compare this file to the similar file:
Show the results in this format:

/*	$OpenBSD: supcmeat.c,v 1.23 2007/09/14 14:29:20 chl Exp $	*/

/*
 * Copyright (c) 1992 Carnegie Mellon University
 * All Rights Reserved.
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 *
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 * sup "meat" routines
 **********************************************************************
 * HISTORY
 *
 * 7-July-93  Nate Williams at Montana State University
 *	Modified SUP to use gzip based compression when sending files
 *	across the network to save BandWidth
 *
 * Revision 1.16  92/09/09  22:04:51  mrt
 * 	Really added bww's recvone changes this time. 
 * 	Added code to support non-crypting version of sup.
 * 	[92/09/01            mrt]
 * 
 * Revision 1.15  92/08/11  12:07:09  mrt
 * 	Added support to add release to FILEWHEN name.
 * 	Updated variable arguemnt list usage - bww
 * 	Updated recvone() to take a va_list - bww
 * 	Changed conditional for rpausing code from CMUCS to MACH
 * 	[92/07/24            mrt]
 * 
 * Revision 1.14  92/02/08  18:24:12  mja
 * 	Only apply "keep" mode when local file is strictly newer
 * 	otherwise allow update as before if necessary.
 * 	[92/02/08  18:09:00  mja]
 * 
 * 	Added support for -k (keep) option to needone().  Rewrote and
 * 	commented other parts of needone().
 * 	[92/01/17            vdelvecc]
 * 
 * Revision 1.13  91/05/16  14:49:41  ern
 * 	Add timestap to fileserver.
 * 	Drop day of the week from 5 messages.
 * 	[91/05/16  14:47:53  ern]
 * 
 * Revision 1.12  89/08/23  14:55:44  gm0w
 * 	Changed msgf routines to msg routines.
 * 	[89/08/23            gm0w]
 * 
 * Revision 1.11  89/08/03  19:49:10  mja
 * 	Updated to use v*printf() in place of _doprnt().
 * 	[89/04/19            mja]
 * 
 * Revision 1.10  89/06/18  14:41:27  gm0w
 * 	Fixed up some notify messages of errors to use "SUP:" prefix.
 * 	[89/06/18            gm0w]
 * 
 * Revision 1.9  89/06/10  15:12:17  gm0w
 * 	Changed to always use rename to install targets.  This breaks hard
 * 	links and recreates those known to sup, other links will be orphaned.
 * 	[89/06/10            gm0w]
 * 
 * Revision 1.8  89/05/24  15:04:23  gm0w
 * 	Added code to check for EINVAL from FSPARAM ioctl for disk
 * 	space check failures when the ioctl is not implemented.
 * 	[89/05/24            gm0w]
 * 
 * Revision 1.7  89/01/16  18:22:28  gm0w
 * 	Changed needone() to check that mode of files match before
 * 	setting update if times also match.
 * 	[89/01/16            gm0w]
 * 
 * 10-Feb-88  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Added timeout to backoff.
 *
 * 27-Dec-87  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Added crosspatch support.
 *
 * 09-Sep-87  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Added code to be less verbose when updating files that have
 *	already been successfully upgraded.
 *
 * 28-Jun-87  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Added code for "release" support.
 *
 * 26-May-87  Doug Philips (dwp) at Carnegie-Mellon University
 *	Converted to end connection with more information.
 *	Added done routine.  Modified goaway routine to free old
 *	goawayreason.
 *
 * 26-May-87  Doug Philips (dwp) at Carnegie-Mellon University
 *	Use computeBackoff from scm instead of doing it ourselves.
 *
 * 25-May-87  Doug Philips (dwp) at Carnegie-Mellon University
 *	Split off from sup.c and reindented goaway calls.
 *
 **********************************************************************
 */

#include "supcdefs.h"
#include "supextern.h"
#include <sys/param.h>
#include <sys/wait.h>

TREE *lastT;				/* last filenames in collection */
jmp_buf sjbuf;				/* jump location for network errors */
int dontjump;				/* flag to void sjbuf */
int cancompress = FALSE;		/* Can we do compression? */
int docompress = FALSE;			/* Do we do compression? */

extern COLLECTION *thisC;		/* collection list pointer */
extern int rpauseflag;			/* don't disable resource pausing */
extern int portdebug;			/* network debugging ports */
extern int noutime;			/* don't set utimes */

/*************************************************
 ***    U P G R A D E   C O L L E C T I O N    ***
 *************************************************/

static int needone(TREE *, void *);
static int recvone(TREE *, va_list);
static int denyone(TREE *, void *);
static int deleteone(TREE *, void *);
static int linkone(TREE *, void *);
static int execone(TREE *, void *);
static int finishone(TREE *, void *);


/*
 * The next two routines define the fsm to support multiple fileservers
 * per collection.
 */
int
getonehost(t, v)
	TREE *t;
	void *v;
{
	long *state = v;

	if (t->Tflags != *state)
		return (SCMOK);
	if (*state != 0 && t->Tmode == SCMEOF) {
		t->Tflags = 0;
		return (SCMOK);
	}
	if (*state == 2)
		t->Tflags--;
	else
		t->Tflags++;
	thisC->Chost = t;
	return (SCMEOF);
}

TREE *
getcollhost(tout, backoff, state, nhostsp)
	int *tout, *backoff, *nhostsp;
	long *state;
{
	static long laststate = 0;
	static int nhosts = 0;

	if (*state != laststate) {
		*nhostsp = nhosts;
		laststate = *state;
		nhosts = 0;
	}
	if (Tprocess(thisC->Chtree, getonehost, state) == SCMEOF) {
		if (*state != 0 && nhosts == 0 && !dobackoff(tout, backoff))
			return (NULL);
		nhosts++;
		return (thisC->Chost);
	}
	if (nhosts == 0)
		return (NULL);
	if (*state == 2)
		(*state)--;
	else
		(*state)++;
	return (getcollhost(tout, backoff, state, nhostsp));
}

/*
 *  Upgrade a collection from the file server on the appropriate
 *  host machine.
 */
void
getcoll(void)
{
	TREE *t;
	int x;
	int tout, backoff, nhosts;
	long state;

	collname = thisC->Cname;
	tout = thisC->Ctimeout;
	lastT = NULL;
	backoff = 2;
	state = 0;
	nhosts = 0;
	for (;;) {
		t = getcollhost(&tout, &backoff, &state, &nhosts);
		if (t == NULL) {
			finishup(SCMEOF);
			notify(NULL);
			return;
		}
		t->Tmode = SCMEOF;
		dontjump = FALSE;
		if (!setjmp(sjbuf) && !signon(t, nhosts, &tout) && !setup(t))
			break;
		(void) requestend();
	}
	dontjump = FALSE;
	if (setjmp(sjbuf))
		x = SCMERR;
	else {
		suplogin();
		listfiles();
		recvfiles();
		x = SCMOK;
	}
	if (thisC->Clockfd >= 0) {
		(void) close(thisC->Clockfd);
		thisC->Clockfd = -1;
	}
	finishup(x);
	notify(NULL);
}

/***  Sign on to file server ***/

int signon (t, nhosts, tout)
	TREE *t;
	int nhosts;
	int *tout;
{
	int x;
	int timeout;
	time_t tloc;

	if ((thisC->Cflags&CFLOCAL) == 0 && thishost(thisC->Chost->Tname)) {
		vnotify("SUP: Skipping local collection %s\n", collname);
		t->Tmode = SCMEOF;
		return (TRUE);
	}
	if (nhosts == 1)
		timeout = *tout;
	else
		timeout = 0;
	x = request(portdebug ? DEBUGFPORT : FILEPORT, thisC->Chost->Tname,
	    &timeout);
	if (nhosts == 1)
		*tout = timeout;
	if (x != SCMOK) {
		if (nhosts) {
			notify("SUP: Can't connect to host %s\n",
			    thisC->Chost->Tname);
			t->Tmode = SCMEOF;
		} else
			t->Tmode = SCMOK;
		return (TRUE);
	}
	xpatch = FALSE;
	x = msgsignon();	/* signon to fileserver */
	if (x != SCMOK)
		goaway("Error sending signon request to fileserver");
	x = msgsignonack();	/* receive signon ack from fileserver */
	if (x != SCMOK)
		goaway("Error reading signon reply from fileserver");
	tloc = time(NULL);
	vnotify("SUP Fileserver %d.%d (%s) %ld on %s at %.8s\n", protver,
	    pgmver, scmver, (long)fspid, remotehost(), ctime(&tloc) + 11);
	free(scmver);
	scmver = NULL;
	if (protver < 4) {
		dontjump = TRUE;
		goaway("Fileserver sup protocol version is obsolete.");
		notify("SUP: This version of sup can only communicate with a fileserver using at least\n");
		notify("SUP: version 4 of the sup network protocol.  You should either run a newer\n");
		notify("SUP: version of the sup fileserver or find an older version of sup.\n");
		t->Tmode = SCMEOF;
		return (TRUE);
	}
	/* If protocol is > 7 then try compression */
	if (protver > 7) {
		cancompress = TRUE;
	}
	return (FALSE);
}

/***  Tell file server what to connect to ***/

int
setup(t)
	TREE *t;
{
	char relsufix[STRINGLENGTH];
	int x;
	struct stat sbuf;

	if (chdir(thisC->Cbase) < 0)
		goaway("Can't change to base directory %s", thisC->Cbase);
	if (stat ("sup", &sbuf) < 0) {
		(void) mkdir("sup", 0755);
		if (stat("sup",&sbuf) < 0)
			goaway("Can't create directory %s/sup", thisC->Cbase);
		vnotify("SUP Created directory %s/sup\n", thisC->Cbase);
	}
	if (thisC->Cprefix && chdir(thisC->Cprefix) < 0)
		goaway("Can't change to %s from base directory %s",
		    thisC->Cprefix, thisC->Cbase);
	if (stat(".", &sbuf) < 0)
		goaway("Can't stat %s directory %s",
			thisC->Cprefix ? "prefix" : "base",
			thisC->Cprefix ? thisC->Cprefix : thisC->Cbase);
	if (thisC->Cprefix)
		(void) chdir(thisC->Cbase);
	/* read time of last upgrade from when file */

	if ((thisC->Cflags&CFURELSUF) && thisC->Crelease)
		(void) snprintf(relsufix, sizeof relsufix, ".%s",
		    thisC->Crelease);
	else
		relsufix[0] = '\0';
	lasttime = getwhen(collname, relsufix);
	/* setup for msgsetup */
	basedir = thisC->Chbase;
	basedev = sbuf.st_dev;
	baseino = sbuf.st_ino;
	listonly = (thisC->Cflags&CFLIST);
	newonly = ((thisC->Cflags&(CFALL|CFDELETE|CFOLD)) == 0);
	release = thisC->Crelease;
	x = msgsetup();
	if (x != SCMOK)
		goaway ("Error sending setup request to file server");
	x = msgsetupack();
	if (x != SCMOK)
		goaway("Error reading setup reply from file server");
	if (setupack == FSETUPOK) {
		/* Test encryption */
		if (netcrypt(thisC->Ccrypt) != SCMOK)
			goaway("Running non-crypting sup");
		crypttest = CRYPTTEST;
		x = msgcrypt();
		if (x != SCMOK)
			goaway("Error sending encryption test request");
		x = msgcryptok();
		if (x == SCMEOF)
			goaway("Data encryption test failed");
		if (x != SCMOK)
			goaway("Error reading encryption test reply");
		return (FALSE);
	}
	switch (setupack) {
	case FSETUPSAME:
		notify("SUP: Attempt to upgrade from same host to same directory\n");
		done(FDONESRVERROR, "Overwrite error");
	case FSETUPHOST:
		notify("SUP: This host has no permission to access %s\n",
		    collname);
		done(FDONESRVERROR, "Permission denied");
	case FSETUPOLD:
		notify("SUP: This version of SUP is too old for the fileserver\n");
		done(FDONESRVERROR, "Obsolete client");
	case FSETUPRELEASE:
		notify("SUP: Invalid release %s for collection %s\n",
			release == NULL ? DEFRELEASE : release, collname);
		done(FDONESRVERROR, "Invalid release");
	case FSETUPBUSY:
		vnotify("SUP Fileserver is currently busy\n");
		t->Tmode = SCMOK;
		doneack = FDONESRVERROR;
		donereason = "Fileserver is busy";
		(void) netcrypt(NULL);
		(void) msgdone();
		return (TRUE);
	default:
		goaway("Unrecognized file server setup status %d", setupack);
	}
	/* NOTREACHED */
	return (FALSE);
}

/***  Tell file server what account to use ***/

void
suplogin(void)
{
	char buf[STRINGLENGTH];
	int f, x;

	/* lock collection if desired */
	(void) snprintf(buf, sizeof buf, FILELOCK, collname);
	f = open(buf, O_RDONLY, 0);
	if (f >= 0) {

#if defined(LOCK_EX)
# define TESTLOCK(f)	flock(f, LOCK_EX|LOCK_NB)
# define SHARELOCK(f)	flock(f, LOCK_SH|LOCK_NB)
# define WAITLOCK(f)	flock(f, LOCK_EX)
#elif defined(F_LOCK)
# define TESTLOCK(f)	lockf(f, F_TLOCK, 0)
# define SHARELOCK(f)	1
# define WAITLOCK(f)	lockf(f, F_LOCK, 0)
#else
# define TESTLOCK(f)	(close(f), f = -1, 1)
# define SHARELOCK(f)	1
# define WAITLOCK(f)	1
#endif
		if (TESTLOCK(f) < 0) {
			if (errno != EWOULDBLOCK && errno != EAGAIN) {
				(void) close(f);
				goaway("Can't lock collection %s", collname);
			}
			if (SHARELOCK(f) < 0) {
				(void) close(f);
				if (errno == EWOULDBLOCK && errno != EAGAIN)
					goaway("Collection %s is locked by another sup",collname);
				goaway("Can't lock collection %s", collname);
			}
			vnotify("SUP Waiting for exclusive access lock\n");
			if (WAITLOCK(f) < 0) {
				(void) close(f);
				goaway("Can't lock collection %s", collname);
			}
		}
		thisC->Clockfd = f;
		vnotify("SUP Locked collection %s for exclusive access\n",
		    collname);
	}
	logcrypt = NULL;
	loguser = thisC->Clogin;
	logpswd = thisC->Cpswd;

#ifndef	CRYPTING  /* Define CRYPTING for backwards compatibility with old supfileservers */
	if (thisC->Clogin != NULL) /* othewise we only encrypt if there is a login id */
#endif	/* CRYPTING */
	{
		logcrypt = CRYPTTEST;
		(void) netcrypt(PSWDCRYPT);	/* encrypt password data */
	}
	x = msglogin();
#ifndef CRYPTING
	if (thisC->Clogin != NULL) 
#endif
		(void) netcrypt(NULL);		/* turn off encryption */
	if (x != SCMOK)
		goaway("Error sending login request to file server");
	x = msglogack();
	if (x != SCMOK)
		goaway("Error reading login reply from file server");
	if (logack == FLOGNG) {
		notify("SUP: %s\n", logerror);
		free(logerror);
		logerror = NULL;
		notify("SUP: Improper login to %s account",
			thisC->Clogin ? thisC->Clogin : "default");
		done(FDONESRVERROR, "Improper login");
	}
	if (netcrypt (thisC->Ccrypt) != SCMOK)	/* restore encryption */
		goaway("Running non-crypting sup");
}

/*
 *  Send list of files that we are not interested in.  Receive list of
 *  files that are on the repository that could be upgraded.  Find the
 *  ones that we need.  Receive the list of files that the server could
 *  not access.  Delete any files that have been upgraded in the past
 *  which are no longer on the repository.
 */
void
listfiles()
{
	char buf[STRINGLENGTH];
	char relsufix[STRINGLENGTH];
	char *p;
	FILE *f;
	int x;

	if ((thisC->Cflags&CFURELSUF) && release)
		(void) snprintf(relsufix, sizeof relsufix, ".%s", release);
	else
		relsufix[0] = '\0';
	(void) snprintf(buf, sizeof buf, FILELAST, collname, relsufix);
	f = fopen(buf, "r");
	if (f) {
		while ((p = fgets(buf, sizeof(buf), f)) != NULL) {
			p[strcspn(p, "\n")] = '\0';
			if (strchr("#;:", *p))
				continue;
			(void) Tinsert(&lastT, p, FALSE);
		}
		(void) fclose(f);
	}
	refuseT = NULL;
	(void) snprintf(buf, sizeof buf, FILEREFUSE, collname);
	f = fopen(buf, "r");
	if (f) {
		while ((p = fgets(buf, sizeof(buf), f)) != NULL) {
			p[strcspn(p, "\n")] = '\0';
			if (strchr("#;:", *p))
				continue;
			(void) Tinsert(&refuseT, p, FALSE);
		}
		(void) fclose(f);
	}
	vnotify("SUP Requesting changes since %s", ctime(&lasttime) + 4);
	x = msgrefuse();
	if (x != SCMOK)
		goaway ("Error sending refuse list to file server");
	listT = NULL;
	x = msglist();
	if (x != SCMOK)
		goaway("Error reading file list from file server");
	if (thisC->Cprefix)
		(void) chdir(thisC->Cprefix);
	needT = NULL;
	(void) Tprocess(listT, needone, NULL);
	Tfree(&listT);
	x = msgneed();
	if (x != SCMOK)
		goaway("Error sending needed files list to file server");
	Tfree(&needT);
	denyT = NULL;
	x = msgdeny();
	if (x != SCMOK)
		goaway("Error reading denied files list from file server");
	if (thisC->Cflags&CFVERBOSE)
		(void) Tprocess(denyT, denyone, NULL);
	Tfree(&denyT);
	if (thisC->Cflags&(CFALL|CFDELETE|CFOLD))
		(void) Trprocess(lastT, deleteone, NULL);
	Tfree(&refuseT);
}

static int
needone(t, dummy)
	TREE *t;
	void *dummy;
{
	TREE *newt;
	int exists, fetch;
	struct stat sbuf;

	newt = Tinsert (&lastT,t->Tname,TRUE);
	if (!newt)
		return (SCMERR);
	newt->Tflags |= FUPDATE;
	fetch = TRUE;
	if ((thisC->Cflags&CFALL) == 0) {
		if ((t->Tflags&FNEW) == 0 && (thisC->Cflags&CFOLD) == 0)
			return (SCMOK);
		if (S_ISLNK(t->Tmode))
			exists = (lstat(t->Tname,&sbuf) == 0);
		else
			exists = (stat(t->Tname,&sbuf) == 0);
		/*
		 * This is moderately complicated:
		 * If the file is the wrong type or doesn't exist, we need to
		 * fetch the whole file.  If the file is a special file, we
		 * rely solely on the server:  if the file changed, we do an
		 * update; otherwise nothing. If the file is a normal file,
		 * we check timestamps.  If we are in "keep" mode, we fetch if
		 * the file on the server is newer, and do nothing otherwise.
		 * Otherwise, we fetch if the timestamp is wrong; if the file
		 * changed on the server but the timestamp is right, we do an
		 * update.  (Update refers to updating stat information, i.e.
		 * timestamp, owner, mode bits, etc.)
		 */
		if (exists && (sbuf.st_mode&S_IFMT) == (t->Tmode&S_IFMT)) {
			if (!S_ISREG(t->Tmode)) {
				if (t->Tflags&FNEW)
					fetch = FALSE;
				else
					return (SCMOK);
			} else if ((thisC->Cflags&CFKEEP) &&
			    sbuf.st_mtime > t->Tmtime) {
				return (SCMOK);
			} else if (sbuf.st_mtime == t->Tmtime) {
				if (t->Tflags&FNEW)
					fetch = FALSE;
				else
					return (SCMOK);
			}
		}
	}
	/* If we get this far, we're either doing an update or a full fetch. */
	newt = Tinsert(&needT, t->Tname, TRUE);
	if (!newt)
		return (SCMERR);
	if (!fetch && S_ISREG(t->Tmode))
		newt->Tflags |= FUPDATE;
	return (SCMOK);
}

static int
denyone(t, v)
	TREE *t;
	void *v;
{

	vnotify("SUP: Access denied to %s\n", t->Tname);
	return (SCMOK);
}

static int
deleteone(t, v)
	TREE *t;
	void *v;
{
	struct stat sbuf, pbuf;
	int x;
	char *name = t->Tname;
	char pname[MAXPATHLEN];

	if (t->Tflags&FUPDATE)		/* in current upgrade list */
		return (SCMOK);
	if (lstat(name, &sbuf) < 0)	/* doesn't exist */
		return (SCMOK);
	/* is it a symbolic link ? */
	if (S_ISLNK(sbuf.st_mode)) {
		if (Tlookup(refuseT, name)) {
			vnotify("SUP Would not delete symbolic link %s\n",
			    name);
			return (SCMOK);
		}
		if (thisC->Cflags&CFLIST) {
			vnotify("SUP Would delete symbolic link %s\n", name);
			return (SCMOK);
		}
		if ((thisC->Cflags&CFDELETE) == 0) {
			notify("SUP Please delete symbolic link %s\n", name);
			t->Tflags |= FUPDATE;
			return (SCMOK);
		}
		x = unlink(name);
		if (x < 0) {
			notify("SUP: Unable to delete symbolic link %s\n",
			    name);
			t->Tflags |= FUPDATE;
			return (SCMOK);
		}
		vnotify("SUP Deleted symbolic link %s\n", name);
		return (SCMOK);
	}
	/* is it a directory ? */
	if (S_ISDIR(sbuf.st_mode)) {
		if (Tlookup(refuseT, name)) {
			vnotify("SUP Would not delete directory %s\n", name);
			return (SCMOK);
		}
		if (thisC->Cflags&CFLIST) {
			vnotify("SUP Would delete directory %s\n", name);
			return (SCMOK);
		}
		if ((thisC->Cflags&CFDELETE) == 0) {
			notify("SUP Please delete directory %s\n", name);
			t->Tflags |= FUPDATE;
			return (SCMOK);
		}
		if (rmdir(name) < 0) {
			(void) chmod(name, sbuf.st_mode|S_IRWXU);
			if (snprintf(pname, sizeof(pname), "%s/..", name) <
			    sizeof(pname)) {
				if (stat(pname, &pbuf) == 0)
					(void) chmod(pname,
					    pbuf.st_mode|S_IRWXU);
			}
			runp("rm", "rm", "-rf", name, 0);
		}
		if (lstat(name, &sbuf) == 0) {
			notify("SUP: Unable to delete directory %s\n", name);
			t->Tflags |= FUPDATE;
			return (SCMOK);
		}
		vnotify("SUP Deleted directory %s\n", name);
		return (SCMOK);
	}
	/* it is a file */
	if (Tlookup(refuseT, name)) {
		vnotify("SUP Would not delete file %s\n", name);
		return (SCMOK);
	}
	if (thisC->Cflags&CFLIST) {
		vnotify("SUP Would delete file %s\n", name);
		return (SCMOK);
	}
	if ((thisC->Cflags&CFDELETE) == 0) {
		notify("SUP Please delete file %s\n", name);
		t->Tflags |= FUPDATE;
		return (SCMOK);
	}
	x = unlink(name);
	if (x < 0) {
		notify("SUP: Unable to delete file %s\n", name);
		t->Tflags |= FUPDATE;
		return (SCMOK);
	}
	vnotify("SUP Deleted file %s\n", name);
	return (SCMOK);
}

/***************************************
 ***    R E C E I V E   F I L E S    ***
 ***************************************/

/*
 * Note for these routines, return code SCMOK generally means
 * NETWORK communication is OK; it does not mean that the current
 * file was correctly received and stored.  If a file gets messed
 * up, too bad, just print a message and go on to the next one;
 * but if the network gets messed up, the whole sup program loses
 * badly and best just stop the program as soon as possible.
 */
void recvfiles (void)
{
	int x;
	int recvmore;

	/* Does the protocol support compression */
	if (cancompress) {
		/* Check for compression on sending files */
		docompress = (thisC->Cflags&CFCOMPRESS);
		x = msgcompress();
		if ( x != SCMOK) 
			goaway("Error sending compression check to server");
		if (docompress)
			vnotify("SUP Using compressed file transfer\n");
	}
	recvmore = TRUE;
	upgradeT = NULL;
	do {
		x = msgsend();
		if (x != SCMOK)
			goaway("Error sending receive file request to file server");
		(void) Tinsert(&upgradeT, NULL, FALSE);
		x = msgrecv(recvone, &recvmore);
		if (x != SCMOK)
			goaway("Error receiving file from file server");
		Tfree(&upgradeT);
	} while (recvmore);
}

/* prepare the target, if necessary */
int
prepare(name, mode, newp, statp)
	char *name;
	int mode, *newp;
	struct stat *statp;
{
	char *type;
	char pname[MAXPATHLEN];
	struct stat pbuf;

	if (mode == S_IFLNK)
		*newp = (lstat(name, statp) < 0);
	else
		*newp = (stat(name, statp) < 0);
	if (*newp) {
		if (thisC->Cflags&CFLIST)
			return (FALSE);
		if (establishdir(name))
			return (TRUE);
		return (FALSE);
	}
	if (mode == (statp->st_mode&S_IFMT))
		return (FALSE);
	*newp = TRUE;
	switch (statp->st_mode&S_IFMT) {
	case S_IFDIR:
		type = "directory";
		break;
	case S_IFLNK:
		type = "symbolic link";
		break;
	case S_IFREG:
		type = "regular file";
		break;
	default:
		type = "unknown file";
		break;
	}
	if (thisC->Cflags&CFLIST) {
		vnotify("SUP Would remove %s %s\n", type, name);
		return (FALSE);
	}
	if (S_ISDIR(statp->st_mode)) {
		if (rmdir (name) < 0) {
			(void) chmod(name, statp->st_mode|S_IRWXU);
			if (snprintf(pname, sizeof(pname), "%s/..", name) <
			    sizeof(pname)) {
				if (stat(pname, &pbuf) == 0)
					(void) chmod(pname,
					    pbuf.st_mode|S_IRWXU);
			}
			runp ("rm", "rm", "-rf", name, 0);
		}
	} else
		(void) unlink(name);
	if (stat(name, statp) < 0) {
		vnotify("SUP Removed %s %s\n", type, name);
		return (FALSE);
	}
	notify("SUP: Couldn't remove %s %s\n", type, name);
	return (TRUE);
}

static int
recvone(t, ap)
	TREE *t;
	va_list ap;
{
	int x = 0;
	int new;
	struct stat sbuf;
	int *recvmore;

	recvmore = va_arg(ap, int *);
	va_end(ap);
	/* check for end of file list */
	if (t == NULL) {
		*recvmore = FALSE;
		return (SCMOK);
	}
	/* check for failed access at fileserver */
	if (t->Tmode == 0) {
		notify("SUP: File server unable to transfer file %s\n",
		    t->Tname);
		thisC->Cnogood = TRUE;
		return (SCMOK);
	}
	if (prepare(t->Tname, t->Tmode&S_IFMT, &new, &sbuf)) {
		notify("SUP: Can't prepare path for %s\n", t->Tname);
		if (S_ISREG(t->Tmode)) {
			x = readskip();		/* skip over file */
			if (x != SCMOK)
				goaway("Can't skip file transfer");
		}
		thisC->Cnogood = TRUE;
		return (SCMOK);
	}
	/* make file mode specific changes */
	switch (t->Tmode&S_IFMT) {
	case S_IFDIR:
		x = recvdir(t, new, &sbuf);
		break;
	case S_IFLNK:
		x = recvsym(t, new, &sbuf);
		break;
	case S_IFREG:
		x = recvreg(t, new, &sbuf);
		break;
	default:
		goaway("Unknown file type %o\n", t->Tmode&S_IFMT);
	}
	if (x) {
		thisC->Cnogood = TRUE;
		return (SCMOK);
	}
	if (S_ISREG(t->Tmode))
		(void) Tprocess(t->Tlink, linkone, t->Tname);
	(void) Tprocess(t->Texec, execone, NULL);
	return (SCMOK);
}

int
recvdir(t, new, statp)		/* receive directory from network */
	TREE *t;
	int new;
	struct stat *statp;
{
	struct timeval tbuf[2];

	if (new) {
		if (thisC->Cflags&CFLIST) {
			vnotify("SUP Would create directory %s\n", t->Tname);
			return (FALSE);
		}
		if (makedir(t->Tname, 0755, statp) == -1) {
			vnotify("SUP: Can't create directory %s\n", t->Tname);
			return TRUE;
		}
	}
	if ((t->Tflags&FNOACCT) == 0) {
		/* convert user and group names to local ids */
		ugconvert(t->Tuser, t->Tgroup, &t->Tuid, &t->Tgid, &t->Tmode);
	}
	if (!new && (t->Tflags&FNEW) == 0 && statp->st_mtime == t->Tmtime) {
		if (t->Tflags&FNOACCT)
			return (FALSE);
		if (statp->st_uid == t->Tuid && statp->st_gid == t->Tgid)
			return (FALSE);
	}
	if (thisC->Cflags&CFLIST) {
		vnotify ("SUP Would update directory %s\n", t->Tname);
		return (FALSE);
	}
	if ((t->Tflags&FNOACCT) == 0) {
		(void) chown(t->Tname, t->Tuid, t->Tgid);
		(void) chmod(t->Tname, t->Tmode&S_IMODE);
	}
	tbuf[0].tv_sec = time(NULL);
	tbuf[0].tv_usec = 0;
	tbuf[1].tv_sec = t->Tmtime;
	tbuf[1].tv_usec = 0;
	if (!noutime)
		(void) utimes(t->Tname, tbuf);
	if (new)
		vnotify("SUP Created directory %s\n", t->Tname);
	else
		v2notify("SUP Updated directory %s\n", t->Tname);
	return (FALSE);
}

int
recvsym(t, new, statp)			/* receive symbolic link */
	TREE *t;
	int new;
	struct stat *statp;
{
	char buf[STRINGLENGTH];
	int n;
	char *linkname;

	if (t->Tlink == NULL || t->Tlink->Tname == NULL) {
		notify("SUP: Missing linkname for symbolic link %s\n",
			t->Tname);
		return (TRUE);
	}
	linkname = t->Tlink->Tname;
	memset(buf, 0, sizeof(buf));
	if (!new && (t->Tflags&FNEW) == 0 &&
	    (n = readlink(t->Tname, buf, sizeof(buf)-1)) >= 0 &&
	    (n == strlen(linkname)) && (strncmp (linkname, buf, n) == 0))
		return (FALSE);
	if (thisC->Cflags&CFLIST) {
		vnotify("SUP Would %s symbolic link %s to %s\n",
			new ? "create" : "update", t->Tname, linkname);
		return (FALSE);
	}
	if (!new)
		(void) unlink(t->Tname);
	if (symlink(linkname, t->Tname) < 0 || lstat(t->Tname, statp) < 0) {
		notify("SUP: Unable to create symbolic link %s\n", t->Tname);
		return (TRUE);
	}
	vnotify("SUP Created symbolic link %s to %s\n", t->Tname, linkname);
	return (FALSE);
}

int
recvreg(t, new, statp)			/* receive file from network */
	TREE *t;
	int new;
	struct stat *statp;
{
	FILE *fin, *fout;
	char dirpart[STRINGLENGTH], filepart[STRINGLENGTH];
	char filename[STRINGLENGTH], buf[STRINGLENGTH];
	struct timeval tbuf[2];
	int x;
	char *p;

	if (t->Tflags&FUPDATE) {
		if ((t->Tflags&FNOACCT) == 0) {
			/* convert user and group names to local ids */
			ugconvert(t->Tuser, t->Tgroup, &t->Tuid, &t->Tgid,
			    &t->Tmode);
		}
		if (!new && (t->Tflags&FNEW) == 0 &&
		    statp->st_mtime == t->Tmtime) {
			if (t->Tflags&FNOACCT)
				return (FALSE);
			if (statp->st_uid == t->Tuid &&
			    statp->st_gid == t->Tgid)
				return (FALSE);
		}
		if (thisC->Cflags&CFLIST) {
			vnotify("SUP Would update file %s\n", t->Tname);
			return (FALSE);
		}
		vnotify("SUP Updating file %s\n", t->Tname);
		if ((t->Tflags&FNOACCT) == 0) {
			(void) chown(t->Tname, t->Tuid, t->Tgid);
			(void) chmod(t->Tname, t->Tmode&S_IMODE);
		}
		tbuf[0].tv_sec = time(NULL);
		tbuf[0].tv_usec = 0;
		tbuf[1].tv_sec = t->Tmtime;
		tbuf[1].tv_usec = 0;
		if (!noutime)
			(void) utimes(t->Tname, tbuf);
		return (FALSE);
	}
	if (thisC->Cflags&CFLIST) {
		if (new)
			p = "create";
		else if (statp->st_mtime < t->Tmtime)
			p = "receive new";
		else if (statp->st_mtime > t->Tmtime)
			p = "receive old";
		else
			p = "receive";
		vnotify("SUP Would %s file %s\n", p, t->Tname);
		return (FALSE);
	}
	vnotify("SUP Receiving file %s\n", t->Tname);
	if (!new && S_ISREG(t->Tmode) &&
	    (t->Tflags&FBACKUP) && (thisC->Cflags&CFBACKUP)) {
		fin = fopen(t->Tname, "r");	/* create backup */
		if (fin == NULL) {
			x = readskip();		/* skip over file */
			if (x != SCMOK)
				goaway("Can't skip file transfer");
			notify("SUP: Can't open %s to create backup\n",
			    t->Tname);
			return (TRUE);		/* mark upgrade as nogood */
		}
		path(t->Tname, dirpart, sizeof dirpart, filepart, sizeof filepart);
		(void) snprintf(filename, sizeof filename, FILEBACKUP,
		    dirpart, filepart);
		fout = fopen(filename, "w");
		if (fout == NULL) {
			(void) snprintf(buf, sizeof buf, FILEBKDIR, dirpart);
			(void) mkdir(buf, 0755);
			fout = fopen(filename, "w");
		}
		if (fout == NULL) {
			x = readskip();		/* skip over file */
			if (x != SCMOK)
				goaway("Can't skip file transfer");
			notify("SUP: Can't create %s for backup\n", filename);
			(void) fclose(fin);
			return (TRUE);
		}
		ffilecopy(fin, fout);
		(void) fclose(fin);
		(void) fclose(fout);
		vnotify("SUP Backup of %s created\n", t->Tname);
	}
	x = copyfile(t->Tname, NULL);
	if (x)
		return (TRUE);
	if ((t->Tflags&FNOACCT) == 0) {
		/* convert user and group names to local ids */
		ugconvert(t->Tuser, t->Tgroup, &t->Tuid, &t->Tgid, &t->Tmode);
		(void) chown(t->Tname, t->Tuid, t->Tgid);
		(void) chmod(t->Tname, t->Tmode&S_IMODE);
	}
	tbuf[0].tv_sec = time(NULL);
	tbuf[0].tv_usec = 0;
	tbuf[1].tv_sec = t->Tmtime;
	tbuf[1].tv_usec = 0;
	if (!noutime)
		(void) utimes(t->Tname, tbuf);
	return (FALSE);
}

static int
linkone(t, fv)			/* link to file already received */
	TREE *t;
	void *fv;
{
	char *fname = fv;
	struct stat fbuf,sbuf;
	char *name = t->Tname;
	int new,x;
	char *type;

	if (stat(fname, &fbuf) < 0) {	/* source file */
		if (thisC->Cflags&CFLIST) {
			vnotify("SUP Would link %s to %s\n", name, fname);
			return (SCMOK);
		}
		notify("SUP: Can't link %s to missing file %s\n", name, fname);
		thisC->Cnogood = TRUE;
		return (SCMOK);
	}
	if (prepare(name,S_IFREG, &new, &sbuf)) {
		notify("SUP: Can't prepare path for link %s\n", name);
		thisC->Cnogood = TRUE;
		return (SCMOK);
	}
	if (!new && (t->Tflags&FNEW) == 0 &&
	    fbuf.st_dev == sbuf.st_dev && fbuf.st_ino == sbuf.st_ino)
		return (SCMOK);
	if (thisC->Cflags&CFLIST) {
		vnotify("SUP Would link %s to %s\n", name, fname);
		return (SCMOK);
	}
	(void) unlink(name);
	type = "";
	if ((x = link(fname,name)) < 0) {
		type = "symbolic ";
		x = symlink(fname, name);
	}
	if (x < 0 || lstat(name, &sbuf) < 0) {
		notify ("SUP: Unable to create %slink %s\n", type, name);
		return (TRUE);
	}
	vnotify("SUP Created %slink %s to %s\n", type, name, fname);
	return (SCMOK);
}

static int
execone(t, v)			/* execute command for file */
	TREE *t;
	void *v;
{
	int w;

	if (thisC->Cflags&CFLIST) {
		vnotify("SUP Would execute %s\n", t->Tname);
		return (SCMOK);
	}
	if ((thisC->Cflags&CFEXECUTE) == 0) {
		notify("SUP Please execute %s\n", t->Tname);
		return (SCMOK);
	}
	vnotify("SUP Executing %s\n", t->Tname);

	w = system(t->Tname);
	if (WIFEXITED(w) && WEXITSTATUS(w) != 0) {
		notify("SUP: Execute command returned failure status %#o\n",
		    WEXITSTATUS(w));
		thisC->Cnogood = TRUE;
	} else if (WIFSIGNALED(w)) {
		notify("SUP: Execute command killed by signal %d\n",
		    WTERMSIG(w));
		thisC->Cnogood = TRUE;
	} else if (WIFSTOPPED(w)) {
		notify("SUP: Execute command stopped by signal %d\n",
		    WSTOPSIG(w));
		thisC->Cnogood = TRUE;
	}
	return (SCMOK);
}

int
copyfile(to, from)
	char *to;
	char *from;		/* 0 if reading from network */
{
	int fromf, tof, istemp, x;
	char dpart[STRINGLENGTH], fpart[STRINGLENGTH];
	char tname[MAXPATHLEN];
	static int true = 1;
	static pid_t thispid = 0;	/* process id # */

	if (from) {			/* reading file */
		fromf = open(from, O_RDONLY, 0);
		if (fromf < 0) {
			notify("SUP: Can't open %s to copy to %s: %s\n",
			    from, to, errmsg(-1));
			return (TRUE);
		}
	} else				/* reading network */
		fromf = -1;
	istemp = TRUE;			/* try to create temp file */
	lockout(TRUE);			/* block interrupts */
	if (thispid == 0)
		thispid = getpid();
	/* Now try hard to find a temp file name.  Try VERY hard. */
	for (;;) {
	/* try destination directory */
		path(to, dpart, sizeof dpart, fpart, sizeof fpart);
		(void) snprintf(tname, sizeof tname, "%s/#%ld.sup.XXXXXXXXXX",
		    dpart, (long)thispid);
		tof = mkstemp(tname);
		if (tof >= 0)
			break;
	/* try sup directory */
		if (thisC->Cprefix)
			(void) chdir (thisC->Cbase);
		(void) snprintf(tname, sizeof tname, "sup/#%ld.sup.XXXXXXXXXX",
		    (long)thispid);
		tof = mkstemp(tname);
		if (tof >= 0) {
			if (thisC->Cprefix)
				(void) chdir(thisC->Cprefix);
			break;
		}
	/* try base directory */
		(void) snprintf(tname, sizeof tname, "#%ld.sup.XXXXXXXXXX",
		    (long)thispid);
		tof = mkstemp(tname);
		if (thisC->Cprefix)
			(void) chdir(thisC->Cprefix);
		if (tof >= 0)
			break;
#ifdef	VAR_TMP
	/* try /var/tmp */
		(void) snprintf(tname, sizeof tname,
		    "/var/tmp/#%ld.sup.XXXXXXXXXX", (long)thispid);
		tof = mkstemp(tname);
		if (tof >= 0)
			break;
#else
	/* try /usr/tmp */
		(void) snprintf(tname, sizeof tname,
		    "/usr/tmp/#%ld.sup.XXXXXXXXXX", (long)thispid);
		tof = mkstemp(tname);
		if (tof >= 0)
			break;
#endif
	/* try /tmp */
		(void) snprintf(tname, sizeof tname,
		    "/tmp/#%ld.sup.XXXXXXXXXX", (long)thispid);
		tof = mkstemp(tname);
		if (tof >= 0)
			break;
		istemp = FALSE;
	/* give up: try to create output file */
		if (!docompress)
			tof = open(to, (O_WRONLY|O_CREAT|O_TRUNC), 0600);
		if (tof >= 0)
			break;
	/* no luck */
		notify("SUP: Can't create %s or temp file for it\n", to);
		lockout (FALSE);
		if (fromf >= 0)
			(void) close (fromf);
		else {
			x = readskip();
			if (x != SCMOK)
				goaway("Can't skip file transfer");
		}
		if (true)
			return (TRUE);
	}
	if (fromf >= 0) {		/* read file */
		x = filecopy(fromf, tof);
		(void) close(fromf);
		(void) close(tof);
		if (x < 0) {
			notify("SUP: Error in copying %s to %s\n", from, to);
			if (istemp)
				(void) unlink(tname);
			lockout(FALSE);
			return (TRUE);
		}
	} else {			/* read network */
#if	MACH
		if (!rpauseflag) {
			int fsize;
			struct fsparam fsp;

			x = prereadcount(&fsize);
			if (x != SCMOK) {
				if (istemp)
					(void) unlink(tname);
				lockout(FALSE);
				x = readskip();
				if (x != SCMOK)
					goaway("Can't skip file transfer");
				goaway("Error in server space check");
				logquit(1, "Error in server space check");
			}
			errno = 0;
			if (ioctl(tof, FIOCFSPARAM, (char *)&fsp) < 0 &&
			    errno != EINVAL) {
				if (istemp)
					(void) unlink(tname);
				lockout(FALSE);
				x = readskip();
				if (x != SCMOK)
					goaway("Can't skip file transfer");
				goaway("Error in disk space check");
				logquit(1, "Error in disk space check");
			}
			if (errno == 0) {
				fsize = (fsize + 1023) / 1024;
				x = fsp.fsp_size * MAX (fsp.fsp_minfree, 1) / 100;
				fsp.fsp_free -= x;
				if (fsize > MAX (fsp.fsp_free, 0)) {
					if (istemp)
						(void) unlink(tname);
					lockout(FALSE);
					x = readskip();
					if (x != SCMOK)
						goaway("Can't skip file transfer");
					goaway("No disk space for file %s", to);
					logquit (1, "No disk space for file %s",
					    to);
				}
			}
		}
#endif	/* MACH */
		x = readfile(tof);
		(void) close(tof);
		if (x != SCMOK) {
			if (istemp)
				(void) unlink(tname);
			lockout(FALSE);
			goaway("Error in receiving %s\n", to);
		}
	}
	if (!istemp) {			/* no temp file used */
		lockout(FALSE);
		return (FALSE);
	}
	/*
	 * If the file is compressed, uncompress it in place.  We open the
	 * temp file for reading, unlink the file, and then open the same
	 * file again for writing.  Then we pipe through gzip.  When 
	 * finished the temp file contains the uncompressed version and we
	 * can continue as before.
	 *
	 * Since sup prefers to write close to the original file the
	 * benefits of atomic updates probably outweigh the cost of the
	 * extra filecopy which occurs when the temp file is on a different
	 * filesystem from the original.
	 */
	if (docompress) {
		char *av[4];
		int   ac = 0;
		int   infd = -1;
		int   outfd = -1;
		av[ac++] = "gzip";
		av[ac++] = "-d";
		av[ac++] = NULL;
		/* XXX - race between unlink and re-open */
		if ((infd = open(tname, O_RDONLY, 0)) == -1 ||
		     unlink(tname) == -1 ||
		     (outfd = open(tname, O_WRONLY|O_CREAT|O_EXCL, 0600)) == -1 ||
		     runiofd(av, infd, outfd, 2) != 0 ) {
			notify("SUP: Error in uncompressing file %s (%s)\n",
				to, tname);
			(void) unlink(tname);
			if (infd != -1)
				(void) close(infd);
			if (outfd != -1)
				(void) close(outfd);
			lockout(FALSE);
			return(TRUE);
		}
		(void) close(infd);
		(void) close(outfd);
	}
	/* move to destination */
	if (rename(tname, to) == 0) {
		(void) unlink(tname);
		lockout(FALSE);
		return(FALSE);
	}
	fromf = open(tname, O_RDONLY, 0);
	if (fromf < 0) {
		notify ("SUP: Error in moving temp file to %s: %s\n",
			to, errmsg (-1));
		(void) unlink(tname);
		lockout(FALSE);
		return (TRUE);
	}
	tof = open(to, O_WRONLY|O_CREAT|O_TRUNC, 0600);
	if (tof < 0) {
		(void) close(fromf);
		notify("SUP: Can't create %s from temp file: %s\n",
			to, errmsg(-1));
		(void) unlink(tname);
		lockout(FALSE);
		return (TRUE);
	}
	x = filecopy(fromf, tof);
	(void) close(fromf);
	(void) close(tof);
	(void) unlink(tname);
	lockout(FALSE);
	if (x < 0) {
		notify("SUP: Error in storing data in %s\n", to);
		return (TRUE);
	}
	return (FALSE);
}

/***  Finish connection with file server ***/

void
finishup(x)
	int x;
{
	char tname[STRINGLENGTH], fname[STRINGLENGTH];
	char relsufix[STRINGLENGTH];
	char collrelname[STRINGLENGTH];
	time_t tloc;
	FILE *finishfile;		/* record of all filenames */

	if ((thisC->Cflags&CFURELSUF) && release) {
		(void) snprintf(relsufix, sizeof relsufix, ".%s", release);
		(void) snprintf(collrelname, sizeof collrelname,
		    "%s-%s", collname, release);
	} else {
		relsufix[0] = '\0';
		(void) strlcpy(collrelname, collname, sizeof collrelname);
	}
	dontjump = TRUE;		/* once here, no more longjmp */
	(void) netcrypt(NULL);
	if (protver < 6) {
		/* done with server */
		if (x == SCMOK)
			goaway(NULL);
		(void) requestend();
	}
	tloc = time(NULL);
	if (x != SCMOK) {
		notify("SUP: Upgrade of %s aborted at %s", collrelname,
		    ctime(&tloc) + 4);
		Tfree(&lastT);
		if (protver < 6)
			return;
		/* if we've not been blown off, make sure he is! */
		if (x != SCMEOF)
			goaway("Aborted");
		(void) requestend();
		return;
	}
	if (thisC->Cnogood) {
		notify("SUP: Upgrade of %s completed with errors at %s",
		    collrelname, ctime(&tloc) + 4);
		notify("SUP: Upgrade time will not be updated\n");
		Tfree(&lastT);
		if (protver < 6)
			return;
		done(FDONEUSRERROR, "Completed with errors");
		(void) requestend();
		return;
	}
	if (thisC->Cprefix)
		(void) chdir(thisC->Cbase);
	vnotify("SUP Upgrade of %s completed at %s", collrelname,
	    ctime(&tloc) + 4);
	if (thisC->Cflags&CFLIST) {
		Tfree(&lastT);
		if (protver < 6)
			return;
		done(FDONEDONTLOG, "List only");
		(void) requestend();
		return;
	}
	(void) snprintf(fname, sizeof fname, FILEWHEN, collname, relsufix);
	if (establishdir(fname)) {
		notify("SUP: Can't create directory for upgrade timestamp\n");
		Tfree(&lastT);
		if (protver < 6)
			return;
		done(FDONEUSRERROR, "Couldn't timestamp");
		(void) requestend();
		return;
	}
	if (!putwhen(fname, scantime)) {
		notify("SUP: Can't record current time in %s: %s\n",
		    fname,errmsg (-1));
		Tfree(&lastT);
		if (protver < 6)
			return;
		done(FDONEUSRERROR,"Couldn't timestamp");
		(void) requestend();
		return;
	}
	if (protver >= 6) {
		/*
		 * At this point we have let the server go
		 * "I'm sorry, we've had to let you go"
		 */
		done(FDONESUCCESS, "Success");
		(void) requestend();
	}
	(void) snprintf(tname, sizeof tname, FILELASTTEMP, collname, relsufix);
	finishfile = fopen(tname, "w");
	if (finishfile == NULL) {
		notify("SUP: Can't record list of all files in %s\n", tname);
		Tfree(&lastT);
		return;
	}
	(void) Tprocess(lastT, finishone, finishfile);
	(void) fclose(finishfile);
	(void) snprintf(fname, sizeof fname, FILELAST, collname, relsufix);
	if (rename(tname, fname) < 0)
		notify("SUP: Can't change %s to %s\n", tname, fname);
	(void) unlink(tname);
	Tfree(&lastT);
}

int
finishone(t, fv)
	TREE *t;
	void *fv;
{
	FILE *finishfile = fv;

	if ((thisC->Cflags&CFDELETE) == 0 || (t->Tflags&FUPDATE))
		fprintf(finishfile, "%s\n", t->Tname);
	return (SCMOK);
}

void
done (int value,char *fmt,...)
{
	char buf[STRINGLENGTH];
	va_list ap;

	va_start(ap, fmt);
	(void) netcrypt(NULL);

	if (fmt) 
		vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);
	if (protver < 6) {
		if (goawayreason)
			free(goawayreason);
		goawayreason = (fmt) ? strdup(buf) : NULL;
		(void) msggoaway();
	}
	else {
		doneack = value;
		donereason = (fmt) ? buf : NULL;
		(void) msgdone();
	}
	if (!dontjump)
		longjmp(sjbuf, TRUE);
}

void
goaway (char *fmt,...)
{
	char buf[STRINGLENGTH];
	va_list ap;

	va_start(ap, fmt);
	(void) netcrypt (NULL);
	if (fmt) {
		vsnprintf(buf, sizeof(buf), fmt, ap);
		goawayreason = buf;
	} else
		goawayreason = NULL;
	va_end(ap);
	(void) msggoaway();
	if (fmt) {
		if (thisC)
			notify("SUP: %s\n", buf);
		else
			printf("SUP: %s\n", buf);
	}
	if (!dontjump)
		longjmp(sjbuf,TRUE);
}