// lookup.C
//
// This program is free software. See the file COPYING for details.
// Author: Mattias Engdegård, 1997-1999

// This module implements asynchronous address->hostname lookup.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <netdb.h>

#include "lookup.h"
#include "svec.cpp"


// declarations of static members
char *Lookup::argv0;
int Lookup::maxtitlelen;

UintQueue::UintQueue()
{
    first = last = 0;
};

void UintQueue::enqueue(unsigned x)
{
    if(last < 0) {
	// make room for inserting more elements
	int step = qMax(first + 1, 8);
	queue.setSize(first + step + 1);
	for(int i = first; i > last; i--)
	    queue[i + step] = queue[i];
	first += step;
	last += step;
    }
    queue.set(last--, x);
}

unsigned UintQueue::dequeue()
{
    if(isEmpty())
	fatal("UintQueue: queue empty");
    return queue[first--];
}

// constructor for node head
Hostnode::Hostnode()
        : next(this), prev(this)
{}

// create a new cache node, initialized with null string
Hostnode::Hostnode(unsigned addr)
        : ipaddr(addr), next(0), prev(0)
{}

// must be called on the head of the list
void Hostnode::moveToFront(Hostnode *node)
{
    if(next != node) {
	Hostnode *p = node->prev, *n = node->next;
	p->next = n;
	n->prev = p;
	node->next = next;
	node->prev = this;
	next->prev = node;
	next = node;
    }
}

// must be called on the head of the list
void Hostnode::deleteLast()
{
    Hostnode *nuke = prev;
    prev = nuke->prev;
    prev->next = this;
    delete nuke;
}

// must be called on the head of the list
void Hostnode::insertFirst(Hostnode *node)
{
    node->prev = this;
    node->next = next;
    next->prev = node;
    next = node;
}

Lookup::Lookup()// : hostdict(17)
{
    sockfd = -1;		// no lookup helper is running
    readsn = writesn = 0;
    outstanding = 0;
}

// empty destructor, workaround for gcc bug
Lookup::~Lookup()
{}

// look up host name (addr is in host byte order)
// a null name means it is been looked up (signal will be sent when done)
QString Lookup::hostname(unsigned addr)
{
    // first look in our cache
    Hostnode *hn = hostdict.value(addr,NULL);
    if(hn) {
	hostlru.moveToFront(hn);
    } else {
	hn = new Hostnode(addr);
	if(hostdict.count() >= hostname_cache_size) {
	    // remove least recently used item
	    hostdict.remove(hostlru.last()->ipaddr);
	    hostlru.deleteLast();
	}
	hostlru.insertFirst(hn);
	hostdict.insert(addr, hn);
	// if(hostdict.count() > hostdict.size() * 3)    hostdict.resize(hostdict.count());
	if(addr == 0)
	    hn->name = "*";
	else
	    request(addr);
    }
    return hn->name;
}

void Lookup::request(unsigned addr)
{
    addrqueue.enqueue(addr);
    if(sockfd < 0) {
	int socks[2];
	socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
	// launch a new helper
	signal(SIGCHLD, SIG_IGN); // Linux does automatic child reaping, nice
	switch(fork()) {
	case -1:	// error
	    return;		// don't bother, we'll try again next time
	case 0:		// child
	    close(socks[0]);
	    do_child(socks[1]);
	    break;
	default:	// parent
	    close(socks[1]);
	    sockfd = socks[0];
	    readsn = new QSocketNotifier(sockfd, QSocketNotifier::Read,
					 this);
	    connect(readsn, SIGNAL(activated(int)), SLOT(receive_result(int)));
	    writesn = new QSocketNotifier(sockfd, QSocketNotifier::Write,
					  this);
	    connect(writesn, SIGNAL(activated(int)), SLOT(send_request(int)));
	    break;
	}
    }
    writesn->setEnabled(TRUE);
}

// the child helper process loop
void Lookup::do_child(int fd)
{
    setproctitle("qps-dns-helper");
    // close unused fds
    for(int i = 0; i < fd; i++)
	close(i);
    for(;;) {
	unsigned addr;
	int ret = read(fd, &addr, sizeof(addr));
	if(ret <= 0) {
	    _exit(0);		// connection closed
	}
	struct hostent *h = gethostbyaddr((char*)&addr, sizeof(addr), AF_INET);
	char buf[256];
	if(!h) {
	    unsigned a = htonl(addr);
	    sprintf(buf, "%d.%d.%d.%d",
		    (a >> 24) & 0xff,
		    (a >> 16) & 0xff,
		    (a >> 8) & 0xff,
		    a & 0xff);
	} else {
	    strncpy(buf, h->h_name, sizeof(buf));
	}
	// if parent died, we'll get SIGPIPE here and terminate automatically
	write(fd, &addr, sizeof(addr));
	int len = strlen(buf);
	write(fd, &len, sizeof(len));
	write(fd, buf, len);
    }
}

// slot: receive result from helper
void Lookup::receive_result(int)
{
    unsigned addr;
    int len;
    char buf[256];

    if(read(sockfd, &addr, sizeof(addr)) <= 0
       || read(sockfd, &len, sizeof(len)) <= 0
       || read(sockfd, buf, len) <= 0) {
	// helper has died
	delete readsn;
	delete writesn;
	close(sockfd);
	sockfd = -1;
	return;
    }
    buf[len] = '\0';
    Hostnode *hn = hostdict.value(addr,NULL);
    if(!hn) return;		// gone from cache
    hn->name = buf;
    emit resolved(addr);

    outstanding--;
    // if there is nothing more in the queue, kill the helper
    if(addrqueue.isEmpty() && outstanding == 0) {
	close(sockfd);
	sockfd = -1;
	delete readsn;
	delete writesn;
    }
}

// slot: send request to the helper
void Lookup::send_request(int)
{
    if(addrqueue.isEmpty()) {
	writesn->setEnabled(FALSE);
	return;
    }

    unsigned addr = addrqueue.dequeue();
    if(write(sockfd, &addr, sizeof(addr)) < 0) {
	// This shouldn't happen, try to repair it anyway
	addrqueue.enqueue(addr);
	delete readsn;
	delete writesn;
	close(sockfd);
	sockfd = -1;
	return;
    }
    outstanding++;
}

// register and measure the space for modifying the visible command line
void Lookup::initproctitle(char **argv, char **envp)
{
    argv0 = argv[0];
    while(*envp) envp++;
    maxtitlelen = envp[-1] + strlen(envp[-1]) - argv0 - 2;
}

// set the process title (idea snarfed from sysvinit (thanks Miquel) and
// refined by peeking into wu-ftpd)
void Lookup::setproctitle(const char *txt)
{
    memset(argv0, 0, maxtitlelen);
    strncpy(argv0, txt, maxtitlelen - 1);
}
