/*
 * hf = cluster is startup, write, read, close, free, insert, delete
 * 
 *	$Source: /mit/tytso/src/gfinger/RCS/hf.c,v $
 *	$Author: tytso $
 *	$Header: hf.c,v 1.4 88/05/24 19:30:17 tytso Exp $
 */

#ifndef lint
static char *rcsid_hf_c = "$Header: hf.c,v 1.4 88/05/24 19:30:17 tytso Exp $";
#endif lint

#include <std.h>
#include <stddef.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/errno.h>
#include <ctype.h>

extern "C" {
#include <netdb.h>
}

#include "gfinger.h"

/*
 * This routine creates and initializes a hf data structure.  The
 * initial connection to the host finger daemon is begun, but it is
 * not completed when the routine returns.  We wait in the select loop
 * for that to happen.
 */
hf::hf(char *name_set, char *host, int port, int large_set)
{
	struct hostent *hp;
	struct sockaddr_in sin;
	int s;

	hp = gethostbyname(host);
	if (hp == NULL) {
		static struct hostent def;
		static struct in_addr defaddr;
		static char *alist[1];
		static char namebuf[128];

		defaddr.s_addr = inet_addr(host);
		if (defaddr.s_addr == -1) {
			printf("unknown host: %s\n", host);
			return (NULL);
		}
		strcpy(namebuf, host);
		def.h_name = namebuf;
		def.h_addr_list = alist, def.h_addr = (char *)&defaddr;
		def.h_length = sizeof (struct in_addr);
		def.h_addrtype = AF_INET;
		def.h_aliases = 0;
		hp = &def;
	}

	sin.sin_family = hp->h_addrtype;
	bcopy(hp->h_addr, (char *)&sin.sin_addr, hp->h_length);
	sin.sin_port = port;
	s = socket(hp->h_addrtype, SOCK_STREAM, 0);
	if (s < 0) {
		fprintf(stderr, "%s: ", hp->h_name);
		perror("socket");
		return(NULL);
	}
	if (fcntl(s, F_SETFL, FNDELAY) > 0) {
		fprintf(stderr, "%s: ", hp->h_name);
		perror("fcntl");
		close(s);
		return(NULL);
	}
	if ((connect(s, (char *)&sin, sizeof (sin)) < 0) &&
	    (errno != EINPROGRESS)) {
		fprintf(stderr, "%s: ", hp->h_name);
		perror("connect");
		close(s);
		return(NULL);
	}
	host = strdup(hp->h_name);
	name = name_set;
	large = large_set;
	fd = s;
	state = STATE_CONNECT;
	data = NULL;
	last = NULL;
	next = NULL;
	prev = NULL;
	time = ::time(0);
	if (debug)
		printf("Host %s, fd = %d\n", host, s);
}

/*
 * This routine writes the request for the finger daemon and
 * switches the state to read mode.
 */
void hf::output()
{
	int	dummy;

	if (read(fd, &dummy, 0) < 0) {
		fprintf(stderr, "%s: ", host);
		perror("hf_write");
		return;
	}
	if (large)
		write(fd, "/W ", 3);
	write(fd, name, strlen(name));
	write(fd, "\r\n", 2);
	state = STATE_READING;
	time = ::time(0);
}

/*
 * This routine handles incoming information from socket associated
 * with the hf data structure.  If an end-of-file is detected, it
 * closes the hf data structure and returns 1.  Otherwise, it reads
 * what data is available and returns 0.
 */
int hf::input()
{
	register int		num_read;
	char			buff[DATA_BUFF_SZ+1], *p, *q, c;
	LONG_STRING		*new_long;
	
	while (1) {
		num_read = read(fd, buff, DATA_BUFF_SZ);
		if (num_read == 0) {
			shutdown();
			time = ::time(0);
			return(1);
		} else if (num_read < 0) {
			if (errno == EWOULDBLOCK) {
				time = ::time(0);
				return(0);
			}
			else if (errno == EINTR)
				continue;
			else if ((errno == ECONNRESET) && !debug) {
				time = ::time(0);
				return(1); /* We lose, silently */
			} else {
				fprintf(stderr, "%s: ", host);
				perror("hf_read");
				shutdown();
				time = ::time(0);
				return(1);
			}
		}
		if (debug)
			printf("(%s - %d) ", host, num_read);
		
		/* Sanitize the buffer */
		for (p=q=buff ; num_read ; num_read--) {
			c = *p++ & 0177;
			if (c != '\r')
				*q++ = c;
		}
		*q++ = '\0';
		
		/* Create new LONG_STRING object */
		new_long = (LONG_STRING *) malloc(sizeof(LONG_STRING));
		new_long->str = strdup(buff);
		new_long->next = NULL;

		/* Now link it in */
		if (last)
			last->next = new_long;
		else
			data = new_long;
		last = new_long;
	}
}

/*
 * This routine will close the socket associated with a hf data
 * structure.
 */
void hf::shutdown()
{
	close(fd);
	state = STATE_EOF;
	if (debug)
		printf("Closing %s, elasped time %d.\n", host,
		       ::time(0) - time);
}

/*
 * This routine will free a hf data structure.
 */
hf::~hf()
{
	register LONG_STRING	*p, *q;
	
	free(host);
	for (p = data; p; p=q) {
		q = p->next;
		free(p);
	}
	if (state & STATE_ACTIVE)
		shutdown();
}

/*
 * This routine deletes hf from the linked list, where top is the
 * address of the pointer to the first element in the list
 */
void hf::delete_ll(hf **top)
{
	if (prev) 
		prev->next = next;
	else
		*top = next;
	if (next)
		next->prev = prev;
}

/*
 * This routine inserts hf after prev_hf in the linked list, where top
 * is the address of the pointer to the first element in the list.  If
 * prev_hf is NULL, then hf is inserted as the first element in the
 * list. 
 */
void hf::insert_ll(hf **top, hf *prev_hf)
{
	next = prev_hf ? prev_hf->next : *top;
	prev = prev_hf; 
	
	if (prev_hf) {
		if (prev_hf->next)
			prev_hf->next->prev = this;
		prev_hf->next = this;
	} else {
		if (*top)
			(*top)->prev = this;
		*top = this;
	}
}

/*
 * Returns a malloc'ed string containing the data passed back from the host
 */
char *hf::get_string()
{
	register int		len;
	register LONG_STRING	*p;
	register char		*str;

	for (len=0, p=data; p; p=p->next)
		len += strlen(p->str);
	*(str = malloc(len+1)) = '\0';
	for (p=data; p; p=p->next)
		(void) strcat(str, p->str);
	return(str);
}

/*
 * Duplicate a string in malloc'ed memory
 */
char *strdup(char *s)
{
	register char	*cp;
	
	if (!(cp = malloc(strlen(s)+1))) {
		printf("Out of memory!!!\n");
		abort();
	}
	return(strcpy(cp,s));
}

