static char rcsid[] = "$Id: collector.c,v 2.2 2000/01/21 17:37:33 sxw Exp $";
/*
 *  collector.c -- Utility procs for add/delete/refresh objects in the Broker.
 *
 *  DEBUG: section  71, level 1         Broker collection routines
 *  AUTHOR: Harvest Derived (William G. Camargo, Darren Hardy)
 *
 *  Harvest Indexer http://harvest.sourceforge.net/
 *  -----------------------------------------------
 *
 *  The Harvest Indexer is a continued development of code developed by
 *  the Harvest Project. Development is carried out by numerous individuals
 *  in the Internet community, and is not officially connected with the
 *  original Harvest Project or its funding sources.
 *
 *  Please mail lee@arco.de if you are interested in participating
 *  in the development effort.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*  ----------------------------------------------------------------------
 *  Copyright (c) 1994, 1995.  All rights reserved.
 *
 *    The Harvest software was developed by the Internet Research Task
 *    Force Research Group on Resource Discovery (IRTF-RD):
 *
 *          Mic Bowman of Transarc Corporation.
 *          Peter Danzig of the University of Southern California.
 *          Darren R. Hardy of the University of Colorado at Boulder.
 *          Udi Manber of the University of Arizona.
 *          Michael F. Schwartz of the University of Colorado at Boulder.
 *          Duane Wessels of the University of Colorado at Boulder.
 *
 *    This copyright notice applies to software in the Harvest
 *    ``src/'' directory only.  Users should consult the individual
 *    copyright notices in the ``components/'' subdirectories for
 *    copyright information about other software bundled with the
 *    Harvest source code distribution.
 *
 *  TERMS OF USE
 *
 *    The Harvest software may be used and re-distributed without
 *    charge, provided that the software origin and research team are
 *    cited in any use of the system.  Most commonly this is
 *    accomplished by including a link to the Harvest Home Page
 *    (http://harvest.cs.colorado.edu/) from the query page of any
 *    Broker you deploy, as well as in the query result pages.  These
 *    links are generated automatically by the standard Broker
 *    software distribution.
 *
 *    The Harvest software is provided ``as is'', without express or
 *    implied warranty, and with no support nor obligation to assist
 *    in its use, correction, modification or enhancement.  We assume
 *    no liability with respect to the infringement of copyrights,
 *    trade secrets, or any patents, and are not responsible for
 *    consequential damages.  Proper use of the Harvest software is
 *    entirely the responsibility of the user.
 *
 *  DERIVATIVE WORKS
 *
 *    Users may make derivative works from the Harvest software, subject
 *    to the following constraints:
 *
 *      - You must include the above copyright notice and these
 *        accompanying paragraphs in all forms of derivative works,
 *        and any documentation and other materials related to such
 *        distribution and use acknowledge that the software was
 *        developed at the above institutions.
 *
 *      - You must notify IRTF-RD regarding your distribution of
 *        the derivative work.
 *
 *      - You must clearly notify users that your are distributing
 *        a modified version and not the original Harvest software.
 *
 *      - Any derivative product is also subject to these copyright
 *        and use restrictions.
 *
 *    Note that the Harvest software is NOT in the public domain.  We
 *    retain copyright, as specified above.
 *
 *  HISTORY OF FREE SOFTWARE STATUS
 *
 *    Originally we required sites to license the software in cases
 *    where they were going to build commercial products/services
 *    around Harvest.  In June 1995 we changed this policy.  We now
 *    allow people to use the core Harvest software (the code found in
 *    the Harvest ``src/'' directory) for free.  We made this change
 *    in the interest of encouraging the widest possible deployment of
 *    the technology.  The Harvest software is really a reference
 *    implementation of a set of protocols and formats, some of which
 *    we intend to standardize.  We encourage commercial
 *    re-implementations of code complying to this set of standards.
 *
 */
#include "broker.h"
#include "log.h"

#define LUPDATE_SIZE MAXHOSTNAMELEN+30

/* Global variables */
extern char *Gather;
extern char *HName;
extern char *DIRpath;
extern char *ColConfig;
extern char *obj_desc;
extern int obj_desc_s;

int new_nobjs, up_nobjs, del_nobjs, ref_nobjs, recv_nobjs, ign_nobjs;
time_t max_update_time = 0;

GathererID *COL_gid = NULL;

/* Local functions */
static int COL_put_last_update();

/* -----------------------------------------------------------------
 * COL_UPD_Obj_begin() -- initialize a new summary object.
 * ----------------------------------------------------------------- */
FILE *COL_UPD_Obj_begin(entry)
     reg_t *entry;
{
	fd_t fd;

	if ((fd = SM_Create_Obj()) == ERROR) {
		errorlog("Collector: Cannot create a new object.\n");
		return NULL;
	}
	entry->FD = fd;
	return (SM_Write_Obj(fd));
}

/* ----------------------------------------------------------------- *
 * COL_Fill_Entry() -- fill in refresh rate and expiration times based
 * on given/default info.
 * ----------------------------------------------------------------- */
int COL_Fill_Entry(entry)
     reg_t *entry;
{
	/*
	 *  All Registry entries MUST have:
	 *      URL, Gatherer-Name, Gatherer-Host, Gatherer-Version,
	 *      and Update-Time
	 *  MD5's are optional, but may used in elimination searches.
	 */
	if (entry->url == NULL) {
		errorlog("%s%s: %s attribute is missing from object: %s\n",
		    COLLECT, ENTRY_ERR, "URL", entry->url);
		return ERROR;
	}
	if (entry->update_time == 0) {
		errorlog("%s%s: %s attribute is missing from object: %s\n",
		    COLLECT, ENTRY_ERR, "Update-Time", entry->url);
		return ERROR;
	}
	if (COL_gid->gn == NULL) {
		errorlog("%s%s: %s attribute is missing from object: %s\n",
		    COLLECT, ENTRY_ERR, "Gatherer-Name", entry->url);
		return ERROR;
	}
	if (COL_gid->gh == NULL) {
		errorlog("%s%s: %s attribute is missing from object: %s\n",
		    COLLECT, ENTRY_ERR, "Gatherer-Host", entry->url);
		return ERROR;
	}
	if (COL_gid->gv == NULL) {
		errorlog("%s%s: %s attribute is missing from object: %s\n",
		    COLLECT, ENTRY_ERR, "Gatherer-Version", entry->url);
		return ERROR;
	}
	COL_gid->GID = -1;
	entry->GID = RG_gid_register(COL_gid);
	if (entry->GID == -1) {
		errorlog("%s%s: illegal Gatherer ID for object.\n",
		    COLLECT, ENTRY_ERR);
		return ERROR;
	}
	/* Set default values */
	if (entry->lmt < 1)
		entry->lmt = 0;
	if (entry->refresh_rate < 1)
		entry->refresh_rate = (time_t) WEEK;

	return SUCCESS;
}

/* ----------------------------------------------------------------- *
 * COL_UPD_Obj_end() -- Add initialized summary object to the Broker.
 * ----------------------------------------------------------------- */
int COL_UPD_Obj_end(entry)
     reg_t *entry;
{
	reg_t *tmp;
	int updating = 0;

	/*
	 *  We want to see if the new object already matches any objects
	 *  in the current Registry.  If it does, then if the new object's
	 *  Update-Time is older than or the same as the Registry object's
	 *  Update-Time, then we ignore the new object.  Otherwise, we need
	 *  to replace the Registry objects with the new object.  We do this
	 *  by deleting the Registry objects, then adding the new object
	 *  to the Registry.  The RG_Cleaner() will run periodically
	 *  to compress the Registry.
	 *
	 *  If the new object is not in the Registry, then it's new
	 *  so we add it to the Registry.
	 */
	while ((tmp = RG_Object_Search_Entry(entry)) != NULL) {
		if (tmp->update_time >= entry->update_time) {
			(void) SM_Destroy_Obj(entry->FD);
			RG_Free_Entry(entry);
			ign_nobjs++;
			if (updating) del_nobjs++; /* There will be no update */
			return SUCCESS;
		} else {
			(void) RG_Clean_Entry(tmp);
			if (updating) del_nobjs++; /* One might be updated, */
			updating = 1;		   /* others are deleted.   */
		}
	}
	if (RG_Register(entry) == ERROR) {
		RG_Free_Entry(entry);
		return ERROR;
	}
	do_IND_New_Object(entry);
	LOGUPDATE(entry);
	if (updating) {
		up_nobjs++;
	} else
		new_nobjs++;
	return SUCCESS;
}

/* -----------------------------------------------------------------
 * COL_DEL_Obj() -- remove an object from the Broker.
 * ----------------------------------------------------------------- */
int COL_DEL_Obj(entry)
     reg_t *entry;
{
	reg_t *tmp;
	int err = SUCCESS;

	if ((tmp = RG_Object_Search_Entry(entry)) != NULL) {
		LOGDELETE(tmp);
		if (RG_Clean_Entry(tmp) == ERROR)
			err = ERROR;
		del_nobjs++;
	} else {
		ign_nobjs++;
	}
	RG_Free_Entry(entry);
	return (err);
}

/* -----------------------------------------------------------------
 * COL_REF_Obj -- update expiration time of an object.
 * ----------------------------------------------------------------- */
int COL_REF_Obj(entry)
     reg_t *entry;
{
	reg_t *tmp;

	/*
	 *  When refreshing the object, all we need to do is save
	 *  the new update_time, then write it to the Registry file.
	 */
	if ((tmp = RG_Object_Search_Entry(entry)) != NULL) {
		/* save new expiration time on disk */
		tmp->update_time = entry->update_time;
		replace_record(tmp);

		LOGREFRESH(entry);
		ref_nobjs++;
		RG_Free_Entry(entry);
		return SUCCESS;
	}
	ign_nobjs++;
	RG_Free_Entry(entry);
	return ERROR;
}

/* ----------------------------------------------------------------- *
 * COL_Save_Att() -- decide which attributes are needed in the registry
 * and save/free it
 * ----------------------------------------------------------------- */
int COL_Save_Att(wlk, entry)
     AVPair *wlk;
     reg_t *entry;
{
	num32 len;
	char *field_name;
	char *value;

	field_name = wlk->attribute;
	value = wlk->value;
	len = (num32) wlk->vsize;
	/* We can assume that these strcmp will only match once per object */
	if (strcmp(field_name, GATH_HOST) == 0) {
		COL_gid->gh = (char *) xmalloc(len + 1);
		memcpy(COL_gid->gh, value, len);
		COL_gid->gh[len] = '\0';
		COL_gid->ghs = len;
		return SUCCESS;
	} else if (strcmp(field_name, GATH_NAME) == 0) {
		COL_gid->gn = (char *) xmalloc(len + 1);
		memcpy(COL_gid->gn, value, len);
		COL_gid->gn[len] = '\0';
		COL_gid->gns = len;
		return SUCCESS;
	} else if (strcmp(field_name, GATH_VER) == 0) {
		COL_gid->gv = (char *) xmalloc(len + 1);
		memcpy(COL_gid->gv, value, len);
		COL_gid->gv[len] = '\0';
		COL_gid->gvs = len;
		return SUCCESS;
	} else if (strcmp(field_name, MD5) == 0) {
		entry->md5 = (char *) xmalloc(len + 1);
		memcpy(entry->md5, value, len);
		entry->md5[len] = '\0';
		entry->md5s = len;
		return SUCCESS;
	} else if (strcmp(field_name, LMT_A) == 0) {
		entry->lmt = (time_t) atol(value);
		if (entry->lmt < 1)
			entry->lmt = 0;
		return SUCCESS;
	} else if (strcmp(field_name, UPDATE_A) == 0) {
		entry->update_time = (time_t) atol(value);
		if (entry->update_time < 1)
			entry->update_time = 0;
		return SUCCESS;
	} else if (strcmp(field_name, TTL) == 0) {
		entry->ttl = (time_t) atol(value);
		if (entry->ttl < 1)
			entry->ttl = 0;
		return SUCCESS;
	} else if (strcmp(field_name, REFRESH_A) == 0) {
		entry->refresh_rate = (time_t) atol(value);
		if (entry->refresh_rate < 1)
			entry->refresh_rate = 0;
		return SUCCESS;
	} else if (strcasecmp(field_name, obj_desc) == 0) {
		if (entry->desc != NULL) {
			/* Ignore duplicate description field */
			return SUCCESS;
		}
#ifdef TRUNCATE_DESCRIPTIONS
		{
			/* Makes all descriptions one-line only */
			int maxdesc = 70, x;
			char *s;

			/* don't malloc too much; we truncate at maxdesc */
			x = ((maxdesc + 10) < len) ? (maxdesc + 10) : len;
			entry->desc = (char *) xmalloc(x + 1);
			memcpy(entry->desc, value, x);
			entry->desc[x] = '\0';
			/* See if chopping at the first newline will do it */
			if ((s = strchr(entry->desc, '\n')) != NULL)
				*s = '\0';
			if (strlen(entry->desc) > maxdesc) {
				/* we'd better just chop off the end */
				entry->desc[maxdesc - 1] = '\0';
				entry->desc[maxdesc - 2] = '.';
				entry->desc[maxdesc - 3] = '.';
				entry->desc[maxdesc - 4] = '.';
			}
			/* reassign buffer */
			s = xstrdup(entry->desc);
			xfree(entry->desc);
			entry->desc = s;
		}
#else
		entry->desc = (char *) xmalloc(len + 1);
		memcpy(entry->desc, value, len);
		entry->desc[len] = '\0';
#endif
		entry->descs = strlen(entry->desc);
		return SUCCESS;
	}
	return ERROR;
}

/* ----------------------------------------------------------------- *
 * COL_Normalize() do some thesaurus, normalization-- changes all
 * field names to lower case
 * ----------------------------------------------------------------- */
char *COL_Normalize_Name(name)
     char *name;
{
	UTIL_Make_Lower(name);
	return (name);
}

static int To_Gatherer[2];

/*
 *  COL_Create_Read_Pipe - creates a read pipe from the gather process.
 */
FILE *COL_Create_Read_Pipe(cmd)
     char *cmd;
{
	int pid;
	static FILE *fp;

	if (pipe(To_Gatherer) < 0) {
		log_errno("pipe");
		return (NULL);
	}
	/* need to use fork() rather than vfork() because of a memory leak */
	if ((pid = fork()) < 0) {
		log_errno("fork");
		return (NULL);
	}
	if (pid == 0) {		/* child */
		char *argv[64];

		/* simple parsing of the command string */
		memset(argv, '\0', sizeof(char *) * 64);
		parse_argv(argv, cmd);

		/* make 'gather' talk with the Broker */
		close(To_Gatherer[0]);
		dup2(To_Gatherer[1], 1);	/* stdout -> write pipe */

		/* close to prevent gather from getting the broker sockets */
		close_all_fds(3);

		/* stdin is /dev/null, stdout is pipe, stderr is broker.out */
		execvp(argv[0], argv);
		perror(argv[0]);
		_exit(1);
	}
	/* parent */
	close(To_Gatherer[1]);
	if ((fp = fdopen(To_Gatherer[0], "r")) == NULL) {
		errorlog("COL_Create_Read_Pipe: fdopen(%d, \"r\") failed.\n",
		    To_Gatherer[0]);
		close(To_Gatherer[0]);
		return (NULL);
	}
	/* Linux has a buffering problem.  Maybe mixing fgets() and     */
	/* getchar() doesn't work?  This fixes it anyway.               */
#ifdef _HARVEST_LINUX_
	setbuf(fp, NULL);
#endif

	return (fp);
}

void COL_Close_Read_Pipe(fp)
     FILE *fp;
{
	(void) fclose(fp);	/* shutdown pipe to gather */
	(void) close(To_Gatherer[0]);
}

/* ----------------------------------------------------------------- *
 * COL_Init_Connect() -- initialize connection to Gatherer/Broker
 * ----------------------------------------------------------------- */
char *COL_Init_Connect(hostname, port, type, lupdate, queri)
     char *hostname;
     int port;
     short type;
     time_t lupdate;
     char *queri;
{
	int csock;
	struct sockaddr_in coll;
	struct hostent *hp;
	char pstr[BUFSIZ];
	FILE *fp;
	static char *ret;


	LOGCOLLECT(hostname, port, lupdate);

	if (type < BAFULL_U) {
		/* Do a collection from a Gatherer */
		Log("Collecting from the Gatherer at %s:%d.\n", hostname, port);
		pstr[0] = '\0';
		switch (type) {
		case UFULL_U:
			Log("Type 0: Full collection each time, without data compression.\n");
			sprintf(pstr, "%s -nocompress %s %d 0",
			    Gather, hostname, port);
			break;
		case UPARTIAL_U:
			Log("Type 1: Incremental collection, without data compression (since %d).\n", lupdate);
			sprintf(pstr, "%s -nocompress %s %d %d",
			    Gather, hostname, port, lupdate);
			break;
		case CFULL_U:
			Log("Type 2: Full collection each time, with data compression.\n");
			sprintf(pstr, "%s %s %d 0", Gather, hostname, port);
			break;
		case CPARTIAL_U:
			Log("Type 3: Incremental collections, with data compression (since %d).\n", lupdate);
			sprintf(pstr, "%s %s %d %d", Gather, hostname, port, lupdate);
			break;
		}

		if ((fp = COL_Create_Read_Pipe(pstr)) == NULL) {
			errorlog("Collector: Pipe failed.\n");
			return NULL;
		}
		ret = (char *) fp;
		return (ret);
	}
	/* Do a collection from a Broker */
	Log("Collecting from the Broker at %s:%d.\n", hostname, port);
	if ((csock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		log_errno("socket");
		return NULL;
	}
	coll.sin_family = AF_INET;
	if ((hp = gethostbyname(hostname)) == NULL) {
		log_errno("gethostbyname");
		errorlog("%s Unknown host %s\n", COLLECT, hostname);
		return NULL;
	}
	memcpy((char *) &coll.sin_addr, (char *) hp->h_addr, hp->h_length);
	coll.sin_port = htons(port);

	if (connect(csock, (struct sockaddr *) &coll, sizeof(coll)) < 0) {
		log_errno("connect");
		return NULL;
	}
	ret = COL_Get_BInput(csock, queri, type, lupdate);
	close(csock);
	return (ret);
}

/* ----------------------------------------------------------------- *
 * COL_Do_Collection -- do a collection from the list in the
 * Collection.conf file
 * ----------------------------------------------------------------- */
int COL_Do_Collection()
{

	FILE *g_file;
	char host[MAXHOSTNAMELEN + 1], queri[BUFSIZ], mspace[BUFSIZ];
	char *ret;
	int port, do_indexing = 0;
	short type;
	time_t lupdate = 0;

	if ((g_file = fopen(ColConfig, "r")) == NULL) {
		errorlog("Collector: Cannot read collections configuration file: %s\n", ColConfig);
		return ERROR;
	}
	Log("Starting collections...\n");
	if (do_IND_Index_Start() == ERROR) {
		errorlog("Collector: Failed to start indexer.\n");
		(void) fclose(g_file);
		return ERROR;
	}
	memset(mspace, '\0', BUFSIZ);
	while (fgets(mspace, BUFSIZ, g_file) != NULL) {
		if (mspace[0] == '#')
			continue;
		memset(host, '\0', MAXHOSTNAMELEN + 1);
		memset(queri, '\0', BUFSIZ);
		/*
		 *  We read the Collection.conf file to get the collection
		 *  points.  For Gatherer collections, queri is: "--".
		 *  For Broker collections 6 & 7, queri is:
		 *  "--FLAGS the flags --QUERY the query"
		 */
		if (sscanf(mspace, "%s %d %hd %[^\n]s\n",
			host, &port, &type, queri) != 4)
			continue;
		new_nobjs = up_nobjs = del_nobjs = ref_nobjs = recv_nobjs = ign_nobjs = 0;
		lupdate = COL_get_last_update(host, port);
		max_update_time = lupdate;
		ret = COL_Init_Connect(host, port, type, lupdate, queri);
		if (ret != NULL) {
			(void) P_parse_input(ret, type);
		} else {
			(void) COL_reset_update(host, port, lupdate);
		}
		/* If we did something, then update the update time */
		if (new_nobjs > 0 || up_nobjs > 0 || del_nobjs > 0 || ref_nobjs > 0) {
			(void) COL_put_last_update(host, port, max_update_time);
			do_indexing = 1;
		}
		Log("Finished collection - received %d objects (added %d, updated %d, deleted %d, refreshed %d, ignored %d).\n", recv_nobjs, new_nobjs, up_nobjs, del_nobjs, ref_nobjs, ign_nobjs);
	}
	(void) fclose(g_file);

	if (do_indexing) {
		/* Should do any/all indexing now */
		if (do_IND_Index_Flush() == ERROR) {
			errorlog("Collector: Index flush failed.\n");
			return ERROR;
		}
		/* Compute statistics */
		if (AD_do_stats() == ERROR) {
			errorlog("Collector: Generate stats failed.\n");
			return ERROR;
		}
	}
	(void) RG_Sync_Registry();
	Log("Finished collections.\n");
	return SUCCESS;
}

/*
 *  parse_bulk_query - Parses the Query in Collection.conf.  Looks like
 *  "--FLAGS the flags --QUERY the query"
 */
static void parse_bulk_query(q, qf, qe)
     char *q, *qf, *qe;
{
	char *s;

	if (!strcmp(q, "--"))
		return;
	if ((s = strstr(q, "--FLAGS")) != NULL)		/* Grab flags */
		strcpy(qf, s + strlen("--FLAGS"));
	if ((s = strstr(q, "--QUERY")) != NULL)		/* Grab expresion */
		strcpy(qe, s + strlen("--QUERY"));
	if ((s = strstr(qe, "--FLAGS")) != NULL)	/* Strip flags */
		*s = '\0';
	if ((s = strstr(qf, "--QUERY")) != NULL)	/* Strip expres. */
		*s = '\0';
}

/* -----------------------------------------------------------------
 * COL_Get_BInput() -- Do a Broker-to-Broker xfer
 * ----------------------------------------------------------------- */
char *COL_Get_BInput(csock, queri, type, when)
     int csock;
     char *queri;
     short type;
     time_t when;
{
	static char *tfile;	/* temporary file for xfer */
	char ibuf[BUFSIZ], command[BUFSIZ];
	char qe[BUFSIZ], qf[BUFSIZ];
	FILE *fp, *i_file;
	int n;

	Debug(71, 1, ("Begin COL_Get_BInput...\n"));
	memset(command, '\0', BUFSIZ);
	memset(qe, '\0', BUFSIZ);
	memset(qf, '\0', BUFSIZ);

	/* Skip the beginning '--' in the query */
	if (strlen(queri) <= 2 &&
	    ((type == BQFULL_U) || (type == BQPARTIAL_U))) {
		errorlog("Collector: Cannot send null query: '%s'.\n", queri);
		return NULL;
	}
	parse_bulk_query(queri, qf, qe);
	if (type == BAFULL_U) {
		Log("Type 4: Full collection each time, without data compression.\n");
		sprintf(command, "#BULK #SINCE 0 #END #ALLB");
	} else if (type == BAPARTIAL_U) {
		Log("Type 5: Incremental collections, without data compression (since %d).\n", when);
		sprintf(command, "#BULK #SINCE %d #END #ALLB", when);
	} else if (type == BQFULL_U) {
		Log("Type 6: Collection based on a query, without data compression.\n");
		sprintf(command, "#BULK #SINCE 0 %s #END %s", qf, qe);
	} else if (type == BQPARTIAL_U) {
		Log("Type 7: Incremental based on a query, without data compression (since %d).\n", when);
		sprintf(command, "#BULK #SINCE %d %s #END %s", when, qf, qe);
	} else {
		errorlog("Illegal collection type %d\n", type);
		return NULL;
	}

	if (write(csock, command, strlen(command)) < 0) {
		log_errno("write");
		return NULL;
	}
	tfile = xstrdup(tempnam(NULL, "bgat"));
	if ((i_file = fopen(tfile, "a+")) == NULL) {
		errorlog("%s%s\n", COLLECT, OPEN_ERR);
		xfree(tfile);
		return NULL;
	}
	memset(ibuf, '\0', BUFSIZ);

	if ((fp = fdopen(csock, "r")) == NULL) {
		log_errno("fdopen");
		fclose(i_file);
		(void) unlink(tfile);
		xfree(tfile);
		return NULL;
	}
	if (fgets(ibuf, BUFSIZ, fp) == NULL) {
		errorlog("Collection: Empty response from remote Broker.\n");
		fclose(i_file);
		(void) unlink(tfile);
		xfree(tfile);
		return NULL;
	}
	/* Check for a version header, and skip it. */
	while (strncmp(ibuf, QMVERSION, 3) == 0) {
		if (fgets(ibuf, BUFSIZ, fp) == NULL) {
			errorlog("Collection: Null response?\n");
			fclose(i_file);
			(void) unlink(tfile);
			xfree(tfile);
			return NULL;
		}
	}
	/* If we don't receive the Bulk succeeded tag, die */
	if (strncmp(ibuf, BULK_SUC, 3) != 0) {
		errorlog("%s\n", ibuf);
		fclose(i_file);
		(void) unlink(tfile);
		xfree(tfile);
		return NULL;
	}
	/* See if they're talking to a Harvest Gatherer, not Broker */
	if (!strncmp(ibuf, "000 - HELLO", strlen("000 - HELLO"))) {
		errorlog("This collection point is to a Harvest GATHERER, not a Broker!!!.\n");
		fclose(i_file);
		(void) unlink(tfile);
		xfree(tfile);
		return NULL;
	}
	/* Read all of the data into a temporary file */
	while ((n = fread(ibuf, 1, BUFSIZ, fp)) > 0) {
		fwrite(ibuf, 1, n, i_file);
	}

	Debug(71, 1, ("Finished COL_Get_BInput...\n"));
	fclose(fp);
	fclose(i_file);
	return (tfile);
}

/* ----------------------------------------------------------------- *
 * COL_get_list_update() --
 * ----------------------------------------------------------------- */
time_t COL_get_last_update(host, port)
     char *host;
     int port;
{
	int lufd;
	char *filename;
	time_t ret = 0;
	int inport, done = 0, n;
	char inbuf[LUPDATE_SIZE];

	filename = UTIL_make_admin_filename("LASTUPDATE");

	if ((lufd = open(filename, O_RDONLY | O_CREAT, 0666)) == -1) {
		errorlog("%s%s :%s:\n", COLLECT, OPEN_ERR, filename);
		xfree(filename);
		return (0);
	}
	xfree(filename);

	memset(inbuf, '\0', LUPDATE_SIZE);

	while (done == 0) {
		if ((n = read(lufd, inbuf, LUPDATE_SIZE)) < 0) {
			log_errno("read");
			ret = 0;
			done = 1;
		}
		if (n == 0) {
			ret = 0;
			done = 1;
		}
		if (strncmp(host, inbuf, MAXHOSTNAMELEN) == 0) {
			inport = atoi(inbuf + MAXHOSTNAMELEN);
			if (inport == port) {
				ret = (time_t) atol(inbuf + MAXHOSTNAMELEN + 10);
				done = 1;
			}
		}
	}
	close(lufd);
	return (ret);
}

/* ----------------------------------------------------------------- *
 * COL_put_last_update() -- write the update time
 * ----------------------------------------------------------------- */
static int COL_put_last_update(host, port, when)
     char *host;
     int port;
     time_t when;
{
	int lufd;
	char *filename;
	off_t here = 0;
	int inport, done = 0, n;
	char inbuf[LUPDATE_SIZE];

	filename = UTIL_make_admin_filename("LASTUPDATE");

	if ((lufd = open(filename, O_RDWR | O_CREAT, 0666)) == -1) {
		fprintf(stderr, "%s%s :%s:\n", COLLECT, OPEN_ERR, filename);
		xfree(filename);
		return (0);
	}
	xfree(filename);

	while (done == 0) {
		here = lseek(lufd, 0, SEEK_CUR);
		memset(inbuf, '\0', LUPDATE_SIZE);
		if ((n = read(lufd, inbuf, LUPDATE_SIZE)) < 0) {
			perror("read");
			close(lufd);
			return 0;
		}
		if (n == 0)
			done = 1;
		if (strncmp(host, inbuf, MAXHOSTNAMELEN) == 0) {
			inport = atoi(inbuf + MAXHOSTNAMELEN);
			if (inport == port)
				done = 1;
		}
	}

	memset(inbuf, '\0', LUPDATE_SIZE);
	strncpy(inbuf, host, MAXHOSTNAMELEN);
	sprintf(inbuf + MAXHOSTNAMELEN, "%d", port);
	sprintf(inbuf + MAXHOSTNAMELEN + 10, "%d", when);

	if (lseek(lufd, here, SEEK_SET) < 0) {
		perror("lseek");
		return 0;
	}
	if (write(lufd, inbuf, LUPDATE_SIZE) < 0) {
		perror("write");
		return 0;
	}
	close(lufd);
	return 1;
}

/* -----------------------------------------------------------------
 * COL_reset_update()  - reverts the update time for the collection point
 * ----------------------------------------------------------------- */
int COL_reset_update(host, port, lup)
     char *host;
     int port;
     time_t lup;
{
	int lufd;
	char *filename;
	off_t here = 0;
	int inport, done = 0, n;
	char inbuf[LUPDATE_SIZE];

	filename = UTIL_make_admin_filename("LASTUPDATE");

	if ((lufd = open(filename, O_RDWR | O_CREAT, 0666)) == -1) {
		errorlog("%s%s :%s:\n", COLLECT, OPEN_ERR, filename);
		xfree(filename);
		return ERROR;
	}
	xfree(filename);

	while (done == 0) {
		here = lseek(lufd, 0, SEEK_CUR);
		if ((n = read(lufd, inbuf, LUPDATE_SIZE)) < 0) {
			log_errno("read");
			close(lufd);
			return ERROR;
		}
		if (n == 0) {
			done = 1;
		}
		if (strncmp(host, inbuf, MAXHOSTNAMELEN) == 0) {
			inport = atoi(inbuf + MAXHOSTNAMELEN);
			if (inport == port) {
				done = 1;
			}
		}
	}

	memset(inbuf, '\0', LUPDATE_SIZE);
	strncpy(inbuf, host, MAXHOSTNAMELEN);
	sprintf(inbuf + MAXHOSTNAMELEN, "%d", port);
	sprintf(inbuf + MAXHOSTNAMELEN + 10, "%d", lup);

	lseek(lufd, here, SEEK_SET);
	if (write(lufd, inbuf, LUPDATE_SIZE) < 0) {
		log_errno("write");
	}
	close(lufd);
	return SUCCESS;
}
