/* ==== finger.c ============================================================
 * Copyright (c) 1993 by Chris Provenzano, proven@athena.mit.edu
 *
 * Copyright (c) 1989 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Tony Nardo of the Johns Hopkins University/Applied Physics Lab.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Chris Provenzano,
 *	the University of California, Berkeley and its contributors.
 * 4. Neither the name of Chris Provenzano, the University nor the names of
 *	  its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY CHRIS PROVENZANO, THE REGENTS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *  1.00 93/08/26 proven
 *      -Pthread redesign of this file.
 *
 *	1.10 95/02/11 proven
 *		-Now that gethostbyname works ....
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1989 The Regents of the University of California.\n\
 @(#) Copyright (c) 1993, 1995 Chris Provenzano.\n\
 @(#) Copyright (c) 1995 Greg Stark.\n\
 All rights reserved.\n";
#endif /* not lint */

#include <sys/param.h>
#include <sys/file.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>

void *netfinger_thread();

void usage(int eval)
{
	fprintf(stderr,
	  "usage: finger [-lps] [-c <net_count>] [-t|T <timeout>] [-f <filename>] [login ...]\n");
	exit(eval);
}

/*
 * These globals are set initialy and then are only read. 
 * They do not need mutexes.
 */
int thread_time = 0, program_timeout = 0, lflag =  0, maxcount = 0;

/*
 * These globals change and therefor do need mutexes
 */
pthread_mutex_t netmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t netcond = PTHREAD_COND_INITIALIZER;
int parse_count = 1;				
int net_count = 0;

void * timeout_thread(void * arg)
{
	sleep(program_timeout);
	exit(0);
}

void * signal_thread(void * arg)
{
	int sig;
	sigset_t  program_signals;
	sigemptyset(&program_signals);
	sigaddset(&program_signals, SIGINT);
	sigwait(&program_signals, &sig);
	exit(0);
}

void thread_done(void * arg) 
{
	pthread_mutex_lock(&netmutex);
	if (arg == (void *)&parse_count) {
		parse_count--;
	}
	if (arg == (void *)&net_count) {
		if (maxcount && (net_count == maxcount)) {
			pthread_cond_broadcast(&netcond);
		}
		net_count--;
	}
	if (net_count || parse_count) {
		pthread_mutex_unlock(&netmutex);
		return;
	}
	exit(0);
}

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif

void * parse_file(void * arg) 
{
	char hostname[MAXHOSTNAMELEN];
	char * filename = arg;
	pthread_attr_t attr;
	pthread_t thread_id;
    char * thread_arg;
	FILE * fp;
	int len;

	netsetupwait();
	pthread_cleanup_push(thread_done, &parse_count);

	/* Parse the file and create a thread per connection */
	if ((fp = fopen(filename, "r")) == NULL) {
		fprintf(stderr, "Can't open file %s\n", filename);
		pthread_exit(NULL);
	}
	pthread_cleanup_push(fclose, fp);

	if (pthread_attr_init(&attr)) {
		fprintf(stderr, "Error: Can't initialize thread attributes\n");
		exit(2);
	}
	pthread_cleanup_push(pthread_attr_destroy, &attr);

	pthread_mutex_lock(&netmutex);
	while (fgets(hostname, MAXHOSTNAMELEN, fp)) {
		/* Wait for a new thread before allocating more memory */
		while (maxcount && (net_count == maxcount)) {
			pthread_cond_wait(&netcond, &netmutex);
		}
		if ((thread_arg = (char *)malloc(len = strlen(hostname))) == NULL) {
			fprintf(stderr, "Error: out of memory\n");
			exit(2);
		}

		hostname[len - 1] = '\0';
		strcpy(thread_arg, hostname);
		pthread_attr_setcleanup(&attr, free, thread_arg);
		if (pthread_create(&thread_id, NULL, netfinger_thread, thread_arg)) {
			fprintf(stderr, "Error: couldn't create another thread\n");
			exit(2);
		}
		net_count++;
	}
	pthread_mutex_unlock(&netmutex);
	pthread_exit(NULL);
}

main(int argc, char **argv)
{
	pthread_t thread_id;
	char ch;

	/* getopt variables */
	extern char *optarg;
	extern int optind;

	pthread_init();

	while ((ch = getopt(argc, argv, "c:f:t:T:ls")) != (char)EOF)
		switch(ch) {
		case 't':	/* Time to let each thread run */
			if ((thread_time = atoi(optarg)) <= 0) {
				usage(1);
			}
			break;	
		case 'T':	/* Time to let entire program run */
			if ((program_timeout = atoi(optarg)) <= 0) {
				usage(1);
			}
			break;	
		case 'f': 	/* Parse file for list of places to finger */
			if (pthread_create(&thread_id, NULL, parse_file, optarg)) {
				fprintf(stderr,"Error: couldn't create parse_file() thread\n");
				exit(1);
			}
			parse_count++;
			break;	
		case 'l': 	/* long format */
			lflag = 1;		
			break;
		case 's': 	/* short format */
			lflag = 0;		
			break;
		case '?':
			usage(0);
		default:
			usage(1);
		}

	/* The rest of the argumants are hosts */
	argc -= optind;
	argv += optind;

	/* Setup timeout thread, if there is one */
	if (program_timeout) {
		if (pthread_create(&thread_id, NULL, timeout_thread, NULL)) {
			fprintf(stderr,"Error: couldn't create program_timeout() thread\n");
			exit(1);
		}
	}

	/* Setup cleanup thread for signals */
	if (pthread_create(&thread_id, NULL, signal_thread, NULL)) {
		fprintf(stderr,"Error: couldn't create signal_timeout() thread\n");
		exit(1);
	}

	/* Push cleanup handler so we don't have to worry about it */
	pthread_cleanup_push(thread_done, &parse_count);

	/* Setup the net and let everyone run */
	netsetup();

	pthread_mutex_lock(&netmutex);

	while (*argv) {
		while (maxcount && (net_count == maxcount)) {
			pthread_cond_wait(&netcond, &netmutex);
		}
		if (pthread_create(&thread_id, NULL, netfinger_thread, *argv)) {
			fprintf(stderr, "Error: couldn't create another thread\n");
			exit(2);
		}
		argv++;
		net_count++;
	}
	pthread_mutex_unlock(&netmutex);
	pthread_exit(NULL);
}

