// lookup.cpp // // 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 #include #include #include #include #include #include #include #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); }