/** * \file info_slave.c * * \brief The information slave process. * * When running PennMUSH under Unix, a second process (info_slave) is * started and the server farms out DNS and ident lookups to the * info_slave, and reads responses from the info_slave * asynchronously. Communication between server and slave is by means * of datagrams on a connected UDP socket. * * info_slave takes one argument, the descriptor of the local socket. * */ #include "copyrite.h" #include "config.h" #ifdef WIN32 #error "info_slave is not currently supported on Windows" #endif #include <stdio.h> #include <stdlib.h> #ifdef I_SYS_TYPES #include <sys/types.h> #endif #ifdef I_SYS_TIME #include <sys/time.h> #endif #ifdef I_SYS_SOCKET #include <sys/socket.h> #endif #ifdef I_NETINET_IN #include <netinet/in.h> #endif #ifdef I_NETDB #include <netdb.h> #endif #include <ctype.h> #include <string.h> #ifdef I_UNISTD #include <unistd.h> #endif #ifdef HAVE_SYS_EVENT_H #include <sys/event.h> #endif #include <errno.h> #include <signal.h> #include "conf.h" #include "externs.h" #include "wait.h" #include "ident.h" #include "mysocket.h" #include "lookup.h" static void reaper(int); int eventwait_init(void); int eventwait_watch_fd_read(int); int eventwait_watch_parent_exit(void); int eventwait(void); enum methods { METHOD_KQUEUE, METHOD_SELECT }; enum methods method = METHOD_SELECT; /** How many simultaneous lookup processes can be running? If more * attempts are made after this limit has been reached, the main * slave processes does them sequentially until some of the subslaves * exit. */ enum { MAX_SLAVES = 5 }; sig_atomic_t children = 0; pid_t child_pids[MAX_SLAVES]; int main(int argc, char *argv[]) { int mush, port; struct request_dgram req; struct response_dgram resp; ssize_t len; pid_t child, netmush = -2; char localport[NI_MAXSERV]; if (argc < 2) { fputs("info_slave needs a port number!\n", stderr); return EXIT_FAILURE; } port = strtol(argv[1], NULL, 10); mush = port; /* We inherit open file descriptors and sockets from parent */ if (new_process_group() < 0) perror("info_slave: making new process group"); #ifdef HAVE_GETPPID netmush = getppid(); #endif if (eventwait_init() < 0) { perror("info_slave: init_eventwait"); return EXIT_FAILURE; } install_sig_handler(SIGCHLD, reaper); if (eventwait_watch_fd_read(mush) < 0) { perror("info_slave: eventwait_add_fd"); return EXIT_FAILURE; } if (eventwait_watch_parent_exit() < 0) perror("info_slave: eventwait_add_fd"); for (;;) { /* grab a request datagram */ int ev = eventwait(); if (ev == mush) len = recv(mush, &req, sizeof req, 0); else if (ev == (int) netmush) { /* Parent process exited. Exit too. */ fputs ("info_slave: Parent mush process exited unexpectedly! Shutting down.\n", stderr); return EXIT_SUCCESS; } else { /* Error? */ if (errno != EINTR) perror("info_slave: do_eventwait"); continue; } if (len == -1 && errno == EINTR) continue; else if (len != (int) sizeof req) { /* This shouldn't happen. */ perror("info_slave: reading request datagram"); return EXIT_FAILURE; } if (children < MAX_SLAVES) { #ifdef HAVE_FORK child = fork(); if (child < 0) { /* Just do the lookup in the main info_slave */ perror("info_slave: unable to fork; doing lookup in master slave"); } else if (child > 0) { /* Parent info_slave; wait for the next request. */ children++; continue; } #else child = 1; #endif } else child = 1; /* Now in the child info_slave or the master with a failed fork. Do a * lookup and send back to the mush. */ memset(&resp, 0, sizeof resp); resp.fd = req.fd; if (getnameinfo(&req.remote.addr, req.rlen, resp.ipaddr, sizeof resp.ipaddr, NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV) != 0) strcpy(resp.ipaddr, "An error occured"); if (getnameinfo(&req.local.addr, req.llen, NULL, 0, localport, sizeof localport, NI_NUMERICHOST | NI_NUMERICSERV) != 0) resp.connected_to = -1; else resp.connected_to = strtol(localport, NULL, 10); if (req.use_ident) { IDENT *ident_result; int timeout = req.timeout; ident_result = ident_query(&req.local.addr, req.llen, &req.remote.addr, req.rlen, &timeout); if (ident_result && ident_result->identifier) { strncpy(resp.ident, ident_result->identifier, sizeof resp.ident); resp.ident[(sizeof resp.ident) - 1] = '\0'; } if (ident_result) ident_free(ident_result); } if (req.use_dns) { if (getnameinfo(&req.remote.addr, req.rlen, resp.hostname, sizeof resp.hostname, NULL, 0, NI_NUMERICSERV) != 0) strcpy(resp.hostname, "An error occured"); } else strcpy(resp.hostname, resp.ipaddr); len = send(mush, &resp, sizeof resp, 0); /* Should never happen. */ if (len != (int) sizeof resp) { perror("info_slave: error writing packet"); return EXIT_FAILURE; } if (child == 0) return EXIT_SUCCESS; } return EXIT_SUCCESS; } static void reaper(int signo) { WAIT_TYPE status; if (mush_wait(-1, &status, WNOHANG) < 0) children--; reload_sig_handler(signo, reaper); } /* Event watching code that tries to use various system-dependant * ways of waiting for a variety of events. * In paritcular, on BSD (Including OS X) systems, it uses kqueue()/kevent() * to wait for a fd to be readable or a process to exit. * On others, it uses select(2) with a timeout and periodic checking of getppid() * to see if the parent netmush process stil exists. */ #define HAVE_SELECT #ifdef HAVE_SELECT static fd_set readers; static int maxd = 0; static pid_t parent_pid = 0; #endif #ifdef HAVE_KQUEUE static int kqueue_id = -1; #endif /** Initialize event loop * \return 0 on success, -1 on failure */ int eventwait_init(void) { #ifdef HAVE_KQUEUE kqueue_id = kqueue(); fputs("info_slave: trying kqueue event loop... ", stderr); if (kqueue_id < 0) perror("error"); else fputs("ok. Using kqueue!\n", stderr); if (kqueue_id >= 0) { method = METHOD_KQUEUE; return 0; } else #endif #ifdef HAVE_SELECT if (1) { fputs("info_slave: trying select event loop... ok. Using select.\n", stderr); FD_ZERO(&readers); method = METHOD_SELECT; return 0; } else #endif { fputs("info_slave: No working event loop method!\n", stderr); errno = ENOTSUP; return -1; } } /** Add a file descriptor to check for read events. * Any number of descriptors can be added. * \param fd the descriptor * \return 0 on success, -1 on failure */ int eventwait_watch_fd_read(int fd) { switch (method) { #ifdef HAVE_KQUEUE case METHOD_KQUEUE:{ struct kevent add; struct timespec timeout; memset(&add, 0, sizeof add); add.ident = fd; add.flags = EV_ADD | EV_ENABLE; add.filter = EVFILT_READ; timeout.tv_sec = 0; timeout.tv_nsec = 0; return kevent(kqueue_id, &add, 1, NULL, 0, &timeout); } #endif #ifdef HAVE_SELECT case METHOD_SELECT: FD_SET(fd, &readers); if (fd >= maxd) maxd = fd + 1; return 0; #endif default: return -1; } } /** Monitor parent process for exiting. * \return 0 on success, -1 on error. */ int eventwait_watch_parent_exit(void) { pid_t parent; #ifdef HAVE_GETPPID parent = getppid(); #else errno = ENOTSUP; return -1; #endif switch (method) { #ifdef HAVE_KQUEUE case METHOD_KQUEUE:{ struct kevent add; struct timespec timeout; memset(&add, 0, sizeof(add)); add.ident = parent; add.flags = EV_ADD | EV_ENABLE; add.filter = EVFILT_PROC; add.fflags = NOTE_EXIT; timeout.tv_sec = 0; timeout.tv_nsec = 0; return kevent(kqueue_id, &add, 1, NULL, 0, &timeout); } #endif #ifdef HAVE_SELECT case METHOD_SELECT: parent_pid = parent; return 0; #endif default: errno = ENOTSUP; return -1; } } /** Wait for an event to occur. Only returns on error or when something * happens. * \return The file descriptor or pid of a triggered event, or -1 on error. */ int eventwait(void) { switch (method) { #ifdef HAVE_KQUEUE case METHOD_KQUEUE:{ struct kevent triggered; int res; while (1) { res = kevent(kqueue_id, NULL, 0, &triggered, 1, NULL); if (res == 1) return triggered.ident; else if (res < 0) return -1; } } #endif #ifdef HAVE_SELECT case METHOD_SELECT:{ /* It's more complex to use select(), since it can only poll * file descriptor events, not process events too. Wake up every * 5 seconds to see if the given pid has turned into 1. */ struct timeval timeout, *tout; int res; if (parent_pid > 0) tout = &timeout; else tout = NULL; while (1) { fd_set local_readers; int n; timeout.tv_sec = 5; timeout.tv_usec = 0; FD_ZERO(&local_readers); for (n = 0; n < maxd; n++) if (FD_ISSET(n, &readers)) FD_SET(n, &local_readers); res = select(maxd, &local_readers, NULL, NULL, tout); if (res > 0) { int n; for (n = 0; n < maxd; n++) if (FD_ISSET(n, &local_readers)) return n; } else if (res == 0 && parent_pid) { #ifdef HAVE_GETPPID if (getppid() == 1) /* Parent rocess no longer exists; parent is now init */ return parent_pid; #endif } else if (res < 0) return -1; } } #endif default: return -1; } }