/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/


#define SEQUENCE_RESOLVE	1


#include	<stdio.h>
#include	"task.h"
#include	"lock_level.h"
#include	"netutils.h"
#include	"memory_debug.h"
#include	"utils.h"
#include	"pri_level.h"

typedef struct resolve_queue {
	struct resolve_queue *	next;
	unsigned		pri:1;
	unsigned		status:3;
#define RQ_IDLE			0
#define RQ_RUN			1
#define RQ_TIMEOUT		2
#define RQ_ABOAT_CATCH_		3
#define RQ_OK_			4
	unsigned int		start_time;
	unsigned int		req_time;
} RESOLVE_QUEUE;

typedef struct resolve_queue_header {
	RESOLVE_QUEUE *		head;
	RESOLVE_QUEUE *		tail;
} RESOLVE_QUEUE_HEADER;

typedef struct resolve_request_t {
	struct resolve_request_t *	next;
	HOST_ADDR *			a;
	char *				name;
	HOST_ENTRY *			hp;
	int				ok;
 } RESOLVE_REQUEST_T;

SEM	resolve_lock,resolve_lock2;
HOST_ENTRY * resolv_cache;
HOST_ENTRY ** prev_cache;

HOST_ENTRY ** del_list;
int del_ptr;
RESOLVE_QUEUE_HEADER resolve_que_header[2];
RESOLVE_QUEUE *	resolve_run;
int aboat_resolve_flag;
int resolve_running = 0;

#define DEL_SIZE	100
#define RESOLV_TICK	3600
#define RESOLV_TIMEOUT		600
#define RESOLV_QUE_TIMEOUT	120
#define RESOLV_MIN_TIMEOUT	2

#define RHE_NOTHING	0
#define RHE_RETRY	1
#define RHE_OK		2

void resolv_tick();
HOST_ENTRY * _err_host_entry(char * name,HOST_ADDR * ap);
void _insert_resolve_queue(RESOLVE_QUEUE * rq);
RESOLVE_QUEUE * _delete_resolve_queue();
void _wakeup_resolve_thread();
int _entry_resolve_call(RESOLVE_QUEUE * rq);
int _exit_resolve_call(RESOLVE_QUEUE * rq);
void wakeup_resolve_thread();
int _return_host_entry(HOST_ENTRY*,char * name,HOST_ADDR*);
HOST_ENTRY * resolve_request(HOST_ADDR * a,char * name);

void resolve_thread();
int resolve_tid;
int resolve_launch;
RESOLVE_REQUEST_T * resolve_que_head;
RESOLVE_REQUEST_T * resolve_que_tail;


void
init_resolve()
{
int i;
	resolve_lock = new_lock(LL_P_RESOLV);
	resolve_lock2 = new_lock(LL_P_RESOLV2);
	del_list = d_alloc(sizeof(HOST_ENTRY*)*DEL_SIZE);
	for ( i = 0 ; i < DEL_SIZE ; i ++ )
		del_list[i] = 0;
	del_ptr = 0;
	new_tick(resolv_tick,RESOLV_TICK,0);


}


void
_insert_resolve_queue(RESOLVE_QUEUE * rq)
{
RESOLVE_QUEUE_HEADER * h;
	h = &resolve_que_header[rq->pri];
	rq->next = 0;
	if ( h->head == 0 )
		h->head = h->tail = rq;
	else {
		h->tail->next = rq;
		h->tail = rq;
	}
}


RESOLVE_QUEUE *
_delete_resolve_queue()
{
RESOLVE_QUEUE * ret;
int pri;
RESOLVE_QUEUE_HEADER * h;
	for ( pri = 0 ; pri < 2 ; pri ++ ) {
		h = &resolve_que_header[pri];
		if ( h->head ) {
			ret = h->head;
			h->head = ret->next;
			if ( h->head == 0 )
				h->tail = 0;
			return ret;
		}
	}
	return 0;
}


void
_wakeup_resolve_thread()
{
RESOLVE_QUEUE * rq2;
int t;

retry:
	if ( resolve_run == 0 ) {
		if ( aboat_resolve_flag ) {
			aboat_resolve_request();
			new_tick(wakeup_resolve_thread,-1,0);
			return;
		}
		else {
			rq2 = _delete_resolve_queue();
			if ( rq2 == 0 )
				return;
			if ( rq2->pri == 1 &&
					get_xltime() - rq2->start_time > RESOLV_QUE_TIMEOUT ) {
				rq2->status = RQ_TIMEOUT;
				wakeup_task((int)rq2);
				return;
			}
			resolve_run = rq2;
			rq2->status = RQ_RUN;
			wakeup_task((int)rq2);
			if ( rq2->pri == 0 ) {
				rq2->start_time = get_xltime();
				new_tick(wakeup_resolve_thread,-RESOLV_MIN_TIMEOUT,0);
			}
		}
	}
	else if ( resolve_run->status == RQ_OK_ ) {
		resolve_run = 0;
		goto retry;
	}
	else if ( resolve_run->pri ) {
		if ( resolve_que_header[0].head == 0 )
			return;
		rq2 = resolve_run;
		resolve_run = 0;
		rq2->status = RQ_IDLE;
		_insert_resolve_queue(rq2);
		aboat_resolve_flag = 1;
		aboat_resolve_request();
		new_tick(wakeup_resolve_thread,-1,0);
		goto retry;
	}
	else {
		if ( (t = get_xltime() - resolve_run->start_time) >= RESOLV_MIN_TIMEOUT ) {
			resolve_run->pri = 1;
			goto retry;
		}
		new_tick(wakeup_resolve_thread,t-RESOLV_MIN_TIMEOUT,0);
		return;
	}
}

void
wakeup_resolve_thread()
{
	lock_task(resolve_lock2);
	_wakeup_resolve_thread();
	unlock_task(resolve_lock2,"wakeup_resolve_thread");
}


int
_entry_resolve_call(RESOLVE_QUEUE * rq)
{

	rq->status = RQ_IDLE;
	rq->start_time = get_xltime();
	_insert_resolve_queue(rq);
	_wakeup_resolve_thread();
	for ( ; rq->status != RQ_RUN ; ) {
		sleep_task((int)rq,resolve_lock2);
		lock_task(resolve_lock2);
	}
	return 0;
}

int
_exit_resolve_call(RESOLVE_QUEUE * rq)
{
	if ( rq->status == RQ_RUN ) {
		rq->status = RQ_OK_;
		_wakeup_resolve_thread();
		return 0;
	}
	aboat_resolve_flag = 0;
	_wakeup_resolve_thread();
	for ( ; rq->status != RQ_RUN ; ) {
		sleep_task((int)rq,resolve_lock2);
		lock_task(resolve_lock2);
	}
	if ( rq->status == RQ_TIMEOUT )
		return 0;
	return -1;
}


HOST_ENTRY *
_search_host_entry_byname(char * name)
{
HOST_ENTRY * ret;
int i;
	if ( name == 0 )
		return 0; 
	prev_cache = &resolv_cache;
	for ( ret = resolv_cache ; ret ; 
			prev_cache = &ret->next,
			ret = ret->next ) {
		for ( i = 0 ; ret->names[i] ; i ++ )
			if ( strcmp(ret->names[i],name) == 0 )
				return ret;
	}
	return 0;
}

HOST_ENTRY *
_search_host_entry_byaddr(HOST_ADDR ip)
{
HOST_ENTRY * ret;
int i;
	prev_cache = &resolv_cache;
	for ( ret = resolv_cache ; ret ;
			prev_cache = &ret->next,
			ret = ret->next ) {
		for ( i = 0 ; i < ret->ips_length ; i ++ ) {
			if ( ret->ips[i].size != ip.size )
				continue;
			if ( memcmp(&ret->ips[i].d,&ip.d,ip.size) )
				continue;
			return ret;
		}
	}
	return 0;
}

HOST_ENTRY *
new_host_entry(int names_size,int ips_size)
{
HOST_ENTRY * ret;
	ret = d_alloc(sizeof(*ret));
	ret->flags = 0;
	ret->names = d_alloc((names_size+1)*sizeof(char*));
	ret->ips = d_alloc(ips_size*sizeof(HOST_ADDR));
	ret->ips_length = ips_size;
	ret->timer = 0;
	ret->canonical = 0;
	return ret;
}

void
free_host_entry(HOST_ENTRY * he)
{
char ** q;
	for ( q = he->names ; *q ; q ++ )
		d_f_ree(*q);
	d_f_ree(he->names);
	d_f_ree(he->ips);
	d_f_ree(he);
}

void
_delete_host_entry(HOST_ENTRY * he)
{
HOST_ENTRY ** hep;
	if ( prev_cache && *prev_cache == he ) {
		hep = prev_cache;
		goto del;
	}
	for ( hep = &resolv_cache ; *hep ; hep = &(*hep)->next )
		if ( *hep == he )
			goto del;
	return;
del:
	*hep = he->next;
	prev_cache = 0;

	if ( del_list[del_ptr] )
		free_host_entry(del_list[del_ptr]);
	del_list[del_ptr++] = he;
	if ( del_ptr >= DEL_SIZE )
		del_ptr = 0;
	return;
}


void
_insert_host_entry(HOST_ENTRY * he)
{
	he->next = resolv_cache;
	resolv_cache = he;
	prev_cache = 0;
}


int
_cmp_he_name(char * name,HOST_ENTRY * he)
{
char ** q;
	for ( q = he->names ; *q ; q ++ )
		if ( strcmp(*q,name) == 0 )
			return 0;
	return -1;
}

int
_cmp_he_addr(HOST_ADDR a,HOST_ENTRY * he)
{
int i;
HOST_ADDR * ap;
	for ( i = 0 ; i < he->ips_length ; i ++ ) {
		ap = &he->ips[i];
		if ( a.type != ap->type )
			continue;
		if ( a.size != ap->size )
			continue;
		if ( memcmp(&a.d,&ap->d,a.size) )
			continue;
		return 0;
	}
	return -1;
}

int
_cmp_host_entry(HOST_ENTRY * he1,HOST_ENTRY * he2)
{
char ** q, ** r;
int i;
	if ( he1->ips_length != he2->ips_length )
		return -1;
	for ( q = he1->names, r = he2->names ; *q && *r ; q ++ , r ++ );
	if ( *q != *r )
		return -1;
	for ( q = he1->names ; *q ; q ++ )
		if ( _cmp_he_name(*q,he2) < 0 )
			return -1;
	for ( i = 0 ; i < he1->ips_length ; i ++ )
		if ( _cmp_he_addr(he1->ips[i],he2) < 0 )
			return -1;
	return 0;
}

HOST_ENTRY * 
_replace_host_entry(HOST_ENTRY * he)
{
int i;
HOST_ENTRY * he1;
	he1 = _search_host_entry_byname(he->names[0]);
	if ( he1 && _cmp_host_entry(he1,he) == 0 )
		return he1;
	for ( i = 0 ; he->names[i] ; i ++ ) {
		he1 = _search_host_entry_byname(he->names[i]);
		if ( he1 == 0 )
			continue;
		wakeup_task((int)he1);
		_delete_host_entry(he1);
	}
	for ( i = 0 ; i  < he->ips_length ; i ++ ) {
		he1 = _search_host_entry_byaddr(he->ips[i]);
		if ( he1 == 0 )
			continue;
		wakeup_task((int)he1);
		_delete_host_entry(he1);
	}
	_insert_host_entry(he);
	return he;
}


void
_host_entry_regulation(HOST_ENTRY * he)
{
char * dom;
char ** q;
char * p;
int len1,len_dom;
char * target;
	dom = get_localdomainname();
	if ( dom == 0 )
		return;
	len_dom = strlen(dom);
	for ( q = he->names ; *q ; q ++ ) {
		if ( strcmp(*q,"localhost") == 0 )
			continue;
		p = *q;
		for ( ; *p && *p != '.' ; p ++ );
		if ( *p == '.' ) {
			len1 = strlen(&p[1]);
			if ( memcmp(&p[1],dom,len1) == 0 ) {
				target = d_alloc(strlen(*q)+len_dom+2);
				*p = 0;
				sprintf(target,"%s.%s",*q,dom);
				d_f_ree(*q);
				*q = target;
			}
		}
		else {
			target = d_alloc(strlen(*q)+len_dom+2);
			sprintf(target,"%s.%s",*q,dom);
			d_f_ree(*q);
			*q = target;
		}
	}
	he->canonical = he->names[0];
}


HOST_ENTRY *
_err_host_entry(char * name,HOST_ADDR * ap)
{
int nsize,ipsize;
HOST_ENTRY * ret;
	if ( name )
		nsize = 2;
	else	nsize = 1;
	if ( ap )
		ipsize = 1;
	else	ipsize = 0;
	ret = new_host_entry(nsize,ipsize);
	if ( name ) {
		ret->names[0] = copy_str(name);
		ret->names[1] = 0;
		ret->canonical = ret->names[0];
	}
	else {
		ret->names[0] = 0;
		ret->canonical = 0;
	}
	if ( ap ) {
		ret->ips[0] = *ap;
	}
	ret->flags |= HEF_ERROR;
	return ret;
}

int
_return_host_entry(HOST_ENTRY * he,char * name,HOST_ADDR * ap)
{
	if ( he == 0 ) {
		he = _err_host_entry(name,ap);
		he->flags = HEF_LOADING;
		return RHE_NOTHING;
	}
	if ( he->flags & HEF_LOADING ) {
		for ( ; he->flags & HEF_LOADING ; ) {
			sleep_task((int)he,resolve_lock2);
			lock_task(resolve_lock2);
		}
		return RHE_RETRY;
	}
	return RHE_OK;
}



void
resolve_thread()
{
RESOLVE_REQUEST_T * n;

	resolve_tid = get_tid();
	for ( ; ; ) {
		lock_task(resolve_lock2);
		if (  resolve_que_head == 0 ) {
			resolve_tid = 0;
			resolve_launch = 0;
			unlock_task(resolve_lock2,"resolve_thread");
			break;
		}
		n = resolve_que_head;
		resolve_que_head = n->next;
		if ( resolve_que_head == 0 )
			resolve_que_tail = 0;
		unlock_task(resolve_lock2,"resolve_thread");

		if ( n->a ) {
			resolve_running = 1;
			n->hp = intr_gethostbyaddr_rr(*n->a);
			resolve_running = 0;
		}
		else if ( n->name ) {
			resolve_running = 1;
			n->hp = intr_gethostbyname_rr(n->name);
			resolve_running = 0;
		}
		else	n->hp = 0;


		lock_task(resolve_lock2);
		n->ok = 1;
		wakeup_task((int)n);
		unlock_task(resolve_lock2,"resolve_thread");
	}
}

HOST_ENTRY *
resolve_request(HOST_ADDR * a,char * name)
{
RESOLVE_REQUEST_T n;
	memset(&n,0,sizeof(n));
	n.a = a;
	n.name = name;
	lock_task(resolve_lock2);
	if ( resolve_que_head ) {
		resolve_que_tail->next = &n;
		resolve_que_tail = &n;
	}
	else resolve_que_head = resolve_que_tail = &n;
	wakeup_task((int)&resolve_que_head);
	if ( resolve_launch == 0 ) {
		create_task(resolve_thread,PRI_FETCH,0);
		resolve_launch = 1;
	}
	for ( ; n.ok == 0 ; ) {
		sleep_task((int)&n,resolve_lock2);
		lock_task(resolve_lock2);
	}
	unlock_task(resolve_lock2,"resolve_request");
	return n.hp;
}

HOST_ENTRY *
r_gethostbyaddr(HOST_ADDR a)
{
HOST_ENTRY * ret, *new_ret;
unsigned int t;
RESOLVE_QUEUE rq;

/*
ss_printf("r_get_hostbyaddr :: %x\n",a.d.v4);
*/
	lock_task(resolve_lock2);
retry2:
	ret = _search_host_entry_byaddr(a);
	switch ( _return_host_entry(ret,0,&a) ) {
	case RHE_OK:
		goto ok;
	case RHE_RETRY:
		goto retry2;
	case RHE_NOTHING:
		break;
	default:
		er_panic("RHE");
	}
	_entry_resolve_call(&rq);
retry:
	unlock_task(resolve_lock2,"r_get_hostbyaddr");

	if ( SEQUENCE_RESOLVE )
		ret = resolve_request(&a,0);
	else	ret = intr_gethostbyaddr_rr(a);

	lock_task(resolve_lock2);
	if ( _exit_resolve_call(&rq) < 0 )
		goto retry;
	if ( rq.status == RQ_TIMEOUT )
		ret = 0;

	t = get_xltime();
	if ( ret ) {
		_host_entry_regulation(ret);
		new_ret = _replace_host_entry(ret);
		if ( new_ret != ret ) {
			free_host_entry(ret);
			ret = new_ret;
		}
		ret->timer = 0;
	}
	else {
		ret = _err_host_entry(0,&a);
		new_ret = _replace_host_entry(ret);
		if ( new_ret != ret ) {
			free_host_entry(ret);
			ret = new_ret;
		}
		ret->timer = 0;
/*
		ret = _search_host_entry_byaddr(a);
		if ( ret && !(ret->flags & HEF_LOGHOST) ) {
			if ( ret->timer == 0 )
				ret->timer = t;
			else if ( t - ret->timer > RESOLV_TIMEOUT ) {
				_delete_host_entry(ret);
				ret = 0;
			}
		}
*/
	}
ok:
	if ( ret )
		ret->access = t;
	if ( ret->flags & HEF_ERROR )
		ret = 0;
	unlock_task(resolve_lock2,"r_gethostbyaddr");
	return ret;
}

HOST_ENTRY *
r_gethostbyname(char * name)
{
HOST_ENTRY * ret, * new_ret;
unsigned int t;
RESOLVE_QUEUE rq;
/*
ss_printf("r_get_hostbyname :: %s\n",name);
*/
	lock_task(resolve_lock2);
retry2:
	ret = _search_host_entry_byname(name);
	switch ( _return_host_entry(ret,name,0) ) {
	case RHE_OK:
		goto ok;
	case RHE_RETRY:
		goto retry2;
	case RHE_NOTHING:
		break;
	default:
		er_panic("RHE");
	}
	_entry_resolve_call(&rq);
retry:
	unlock_task(resolve_lock2,"r_get_hostbyname");

	if ( SEQUENCE_RESOLVE )
		ret = resolve_request(0,name);
	else	ret = intr_gethostbyname_rr(name);

	lock_task(resolve_lock2);
	if ( _exit_resolve_call(&rq) < 0 )
		goto retry;
	if ( rq.status == RQ_TIMEOUT )
		ret = 0;

	t = get_xltime();
	if ( ret ) {
		_host_entry_regulation(ret);
		new_ret = _replace_host_entry(ret);
		if ( new_ret != ret ) {
			free_host_entry(ret);
			ret = new_ret;
		}
		ret->timer = 0;
	}
	else {
		ret = _err_host_entry(name,0);
		new_ret = _replace_host_entry(ret);
		if ( new_ret != ret ) {
			free_host_entry(ret);
			ret = new_ret;
		}
		ret->timer = 0;
/*
		ret = _search_host_entry_byname(name);
		if ( ret && !(ret->flags & HEF_LOGHOST) ) {
			if ( ret->timer == 0 )
				ret->timer = t;
			else if ( t - ret->timer > RESOLV_TIMEOUT ) {
				_delete_host_entry(ret);
				ret = 0;
			}
		}
*/
	}
ok:
	if ( ret )
		ret->access = t;
	if ( ret->flags & HEF_ERROR )
		ret = 0;
	unlock_task(resolve_lock2,"r_gethostbyname");
/*
ss_printf("r_get_hostbyname :: %s OK\n",name);
*/
	return ret;
}

void
resolv_tick()
{
HOST_ENTRY * he;
unsigned int t;
	t = get_xltime();
	lock_task(resolve_lock2);
retry:
	for ( he = resolv_cache ; he ; he = he->next ) {
		if ( he->flags & HEF_LOGHOST )
			continue;
		if ( t - he->access > RESOLV_TICK ) {
			_delete_host_entry(he);
			goto retry;
		}
	}
	unlock_task(resolve_lock2,"resolv_tick");
}

int
cmp_HA(HOST_ADDR a,HOST_ADDR b)
{
	if ( a.type != b.type )
		return -1;
	if ( a.size != b.size )
		return -1;
	if ( memcmp(&a.d,&b.d,a.size) == 0 )
		return 0;
	return -1;
}

