/* ARISA - Entry Point and Main Loop * Copyright (C) 2003, 2004 Carl Ritson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #define WANT_POLL #include "arisa.h" #include "user.h" #include "script-eng.h" #include "admin.h" #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #ifdef HAVE_TERMIOS_H #include #endif #ifndef MAX #define MAX(x,y) ((x) >= (y) ? (x) : (y)) #endif #ifndef MIN #define MIN(x,y) ((x) <= (y) ? (x) : (y)) #endif #define CORE_LIMIT (128*1024*1024) /** global variables **/ global_t *global = NULL; int page_size = 0; /** bot_init * Initialize a default arisa configuration, i.e. first run. */ static global_t *bot_init(void) { char buffer1[512],buffer2[512]; global_t *g; user_t *u; printf("Arisa will now initialise a new configuration.\n"); do { printf("Admin Username: "); readtline_term(stdin,buffer1,sizeof(buffer1)); } while(strlen(buffer1) < 1); u = alloc_user(buffer1); user_set_privs(u,PRIVS_FULL); do { #ifdef HAVE_TERMIOS_H /* code borrowed from glibc (manual/examples/mygetpass.c) */ struct termios old,new; if(tcgetattr(fileno(stdin),&old) == 0) { new = old; new.c_lflag &= ~ECHO; tcsetattr(fileno(stdin),TCSAFLUSH,&new); } #endif /* HAVE_TERMIOS_H */ printf("Admin Password: "); readline_term(stdin,buffer1,sizeof(buffer1)); printf("\n"); printf("Admin Password Confirm: "); readline_term(stdin,buffer2,sizeof(buffer2)); printf("\n"); if(strcmp(buffer1,buffer2) != 0) printf("Password Mismatch\n"); #ifdef HAVE_TERMIOS_H tcsetattr(fileno(stdin),TCSAFLUSH,&old); #endif } while(strcmp(buffer1,buffer2) != 0); user_set_password(u,buffer1); memset(buffer1,0,sizeof(buffer1)); memset(buffer2,0,sizeof(buffer2)); printf("Admin Socket: "); readtline_term(stdin,buffer1,sizeof(buffer1)); printf("Log File: "); readtline_term(stdin,buffer2,sizeof(buffer2)); g = alloc_global(); PTRARR_ADD(&g->users,&g->no_users,u); if(strlen(buffer1) > 0) g->settings->admin_socket = xstrdup(buffer1); if(strlen(buffer2) > 0) g->settings->log = xstrdup(buffer2); g->network_settings->nick = xstrdup("Arisa"); g->network_settings->loginname = xstrdup("arisa"); g->network_settings->realname = xstrdup("Arisa-chan"); return g; } /** bot_shutdown * Initiates the bot shutdown, including saving present configuration * and reaping threads. * Global structure is xfree'd on completion. */ static void bot_shutdown(void) { pqueue_t threads; thread_t *thread; int i; pqueue_init(&threads,0); LOGP(L_INF,"Shutdown: Initiating"); LOGP(L_INF,"Shutdown: Attempting to Save Configuration"); save_global(global); LOGP(L_INF,"Shutdown: Signaling Threads to Shutdown"); se_global_shutdown(&threads); LOCK(global); if(thread_is_running(&(global->hash_thread))) { thread_end(&global->hash_thread); pqueue_push_back(&threads,&global->hash_thread); } for(i = 0; i < global->no_networks; ++i) { network_t *net = global->networks[i]; LOCK(net); if(thread_is_running(&(net->thread))) { pqueue_push_back(&threads,irc_network_stop(net)); } else pqueue_push_back(&threads,&(net->thread)); UNLOCK(net); } for(i = 0; i < global->no_interfaces; ++i) { interface_t *intf = global->interfaces[i]; LOCK(intf); if(thread_is_running(&(intf->thread))) { thread_end(&(intf->thread)); pqueue_push_back(&threads,&(intf->thread)); } UNLOCK(intf); } for(i = 0; i < global->no_admins; ++i) { LOCK(global->admins[i]); global->admins[i]->state = STATE_DELETED; thread_end(&global->admins[i]->admin.thread); pqueue_push_back(&threads,&global->admins[i]->admin.thread); // Again this message is mearly a signal to the thread pqueue_push_back(&global->admins[i]->msgqueue, xstrdup("Shutdown!")); UNLOCK(global->admins[i]); } UNLOCK(global); LOGP(L_INF,"Shutdown: Waiting on Threads, Ja ne"); while((thread = pqueue_pop_front(&threads)) != NULL) thread_join(thread,NULL); if(thread_is_running(&(global->log_thread))) { thread_end(&(global->log_thread)); thread_join(&(global->log_thread),NULL); } admin_global_free(); validity_cache_free(); free_global(global); global = NULL; } static int should_shutdown(time_t now) { int ret = 1; int i,j; LOCK(global); if(global->end != 0) { if(global->end_time == TIME_MAX) { for(i = 0; i < global->no_interfaces && ret; ++i) { interface_t *intf = global->interfaces[i]; LOCK(intf); if(intf->type == INTERFACE_SEND) { for(j = 0; j < AVGSPEED && ret; ++j) { if(intf->avgspeed[j] != 0) ret = 0; } } UNLOCK(intf); } } else if(global->end_time > now) { ret = 0; } } else { ret = 0; } UNLOCK(global); return ret; } static void check_log_thread(void) { LOCK(global->settings); if(global->settings->log != NULL) { if(!thread_is_running(&(global->log_thread))) { thread_join(&(global->log_thread),NULL); thread_create(&(global->log_thread),log_thread,NULL); } } else { if(thread_is_running(&(global->log_thread))) thread_end(&(global->log_thread)); else thread_join(&(global->log_thread),NULL); } UNLOCK(global->settings); } static void check_hash_thread(void) { LOCK(global->settings); if(global->settings->hash_packs != 0) { if(!thread_is_running(&(global->hash_thread))) { thread_join(&(global->hash_thread),NULL); thread_create(&(global->hash_thread),hash_thread,NULL); } } else { if(thread_is_running(&(global->hash_thread))) thread_end(&(global->hash_thread)); else thread_join(&(global->hash_thread),NULL); } UNLOCK(global->settings); } static void start_threads(void) { int i; LOCK(global); check_log_thread(); check_hash_thread(); for(i = 0; i < global->no_networks; ++i) { network_t *net = global->networks[i]; LOCK(net); if(net->deleted == 0 && !net->settings->no_autoconnect && !thread_is_running(&(net->thread))) { irc_network_run(net); } UNLOCK(net); } for(i = 0; i < global->no_interfaces; ++i) { interface_t *intf = global->interfaces[i]; LOCK(intf); if(intf->deleted == 0 && !thread_is_running(&(intf->thread))) { interface_start(intf); } UNLOCK(intf); } UNLOCK(global); } #ifdef HAVE_SYS_UN_H static int rehash_admin_socket(int sok, char **asfn) { char *nasfn = NULL; int rehash = 0; LOCK(global->settings); if(*asfn == NULL) { if(global->settings->admin_socket != NULL) { nasfn = xstrdup(global->settings->admin_socket); rehash = 1; } } else if(sok != -1) { if(global->settings->admin_socket != NULL) { if(strcmp(*asfn,global->settings->admin_socket) != 0) { nasfn = xstrdup(global->settings->admin_socket); rehash = 1; } } else { rehash = 1; } } else { nasfn = *asfn; rehash = 1; } UNLOCK(global->settings); if(rehash) { if(sok != -1) { LOGP(L_INF,"Closed Admin Socket%s%s", *asfn != NULL ? ": " : "", *asfn != NULL ? *asfn: ""); admin_socket_close(*asfn,sok); sok = -1; } if(*asfn != nasfn) { if(*asfn != NULL) xfree(*asfn); *asfn = nasfn; } if(*asfn != NULL) { sok = admin_socket_open(*asfn); if(sok != -1) { LOGP(L_INF,"Listening on Admin Socket: %s", *asfn); } else { char errbuf[96]; LOGP(L_ERR,"Opening Admin Socket %s, Error: %s", *asfn, lstrerror_r(errno,errbuf,sizeof(errbuf)) == 0 ? errbuf : "Unknown"); } } } return sok; } #else /* HAVE_SYS_UN_H */ static inline void rehash_admin_socket(int sok, char **asfn) { return -1; } #endif static void poll_and_sleep(int sok) { #define SIG 0 #define SOK 1 struct pollfd fds[2]; struct timeval begin, end; long tosleep; int ret; fds[SIG].fd = global->signal_out; fds[SIG].events = POLLIN; fds[SOK].fd = sok; fds[SOK].events = POLLIN; tosleep = 15*1000; while(!global->end && tosleep > 0) { fds[SOK].revents = 0; fds[SIG].revents = 0; gettimeofday(&begin,NULL); ret = poll(fds, sok != -1 ? 2 : 1, tosleep); gettimeofday(&end,NULL); tosleep -= diff_tv_usec(&begin,&end) / 1000; if(fds[SIG].revents & POLLIN) { char sig = 0; read(fds[SIG].fd,&sig,1); if(sig == SIGTERM) { LOCK(global); global->end = 1; global->end_time = 0; UNLOCK(global); break; } else { se_signal(sig); } } if(fds[SOK].revents & POLLIN) admin_socket_poll(sok,0,0); } #undef SIG #undef SOK } /** bot_main * Main loop of the bot while running, performs all periodic support * functions, starting and merging of threads, etc. * This loop terminates when the a bot shutdown is signaled. */ static void *bot_main(void *arg) { time_t last_purge = 0, last_notify = 0, last_scan = 0, last_tick = xtime(), last_save = xtime(); time_t now; char *asfn = NULL; int tmp, sok = -1; for(;;) { now = xtime(); if(should_shutdown(now)) break; check_log_thread(); check_hash_thread(); LOCK(global->settings); if((tmp = global->settings->autosave_interval) == 0) tmp = 15 * 60; if(test_update_timer(now,&last_tick, global->settings->tick_interval)) LOGP(L_INF,"Main Thread, TICK!"); UNLOCK(global->settings); if(test_update_timer(now,&last_save,tmp)) save_global(global); if(test_update_timer(now,&last_purge,55)) { aresolv_manage(global->aresolver); validity_cache_manage(); admin_purge(now); packlist_purge(now); queue_purge(now); user_purge(now); interface_purge(now); irc_networks_purge(now); } if(test_update_timer(now,&last_scan,30)) packlists_scan(now); if(test_update_timer(now,&last_notify,60)) queue_notify(now); sok = rehash_admin_socket(sok,&asfn); poll_and_sleep(sok); } if(sok != -1) admin_socket_close(asfn,sok); if(asfn != NULL) xfree(asfn); return NULL; } /** sigterm * A silent SIGTERM handler. */ static void sigterm(int sig) { exit(0); } /** to_background * Moves the process into the background, detaching it from the terminal. * There seems to be some odd issues with forks so synchronisation * is done using a pipe. */ static void to_background(void) { pid_t ret, parent = getpid(); char buffer[4]; int pfds[2] = { -1, -1 }; int fd; if(pipe(pfds) == -1) { fprintf(stderr,"Unable to fork: pipe setup failed.\n"); exit(0); } signal(SIGTERM,sigterm); fprintf(stdout,"Going to background.\n"); if((ret = fork()) != 0) { if(ret == -1) { fprintf(stderr,"Unable to fork.\n"); exit(0); } // tell the child we are ready to die write(pfds[1],"\0",1); // wait for term signal for(;;) { sleep(5); } assert(0); } /* We are the child, time to become the session leader. */ // disconnect the terminal close(fileno(stdin)); close(fileno(stdout)); close(fileno(stderr)); #ifdef TIOCNOTTY /* If opening /dev/tty fails, we are most likely not running on an * interactive terminal, ignore the error. */ if((fd = open("/dev/tty",O_RDWR)) != -1) { ioctl(fd,TIOCNOTTY,0); close(fd); } #endif /* TIOCNOTTY */ // become session leader (a new session) setsid(); // wait for parent to say it's ready to be killed read(pfds[0],buffer,1); // good bye ~ kill(parent,SIGTERM); // clean up close(pfds[0]); close(pfds[1]); } void arisa_signal_end(void) { char byte = SIGTERM; write(global->signal_in,&byte,1); } /** signal_handler * This is the bots global signal handler, at present it traps * signal that indicate the bot should shutdown and signals the shutdown * to the rest of the bot. */ static void signal_handler(int signo) { char byte; if(global == NULL) return; switch (signo) { case SIGINT: case SIGQUIT: case SIGTERM: #ifdef SIGPWR case SIGPWR: #endif byte = SIGTERM; break; default: byte = signo; break; } write(global->signal_in,&byte,1); } /** setup_signals * This registrers the signal_handler with appropriate signals, * and setups up ignoring of unwanted signals. */ static void setup_signals(void) { signal(SIGUSR1,signal_handler); signal(SIGUSR2,signal_handler); signal(SIGHUP,signal_handler); signal(SIGINT,signal_handler); signal(SIGQUIT,signal_handler); signal(SIGPIPE,SIG_IGN); signal(SIGTERM,signal_handler); #ifdef SIGPWR signal(SIGPWR,signal_handler); #endif #ifdef SIGIO signal(SIGIO,SIG_IGN); #endif } /** bot_startup * Loads an availible config file, or calls bot_init to create one. * If desired sends the bot into the background. */ static void bot_startup(const char *config, int nobackground, int noxdcc) { FILE *fh; struct stat sbuf; pid_t pid; int i,ret; fprintf(stdout,"Starting: %s\n",ARISA_VERSION); ret = stat(config,&sbuf); if(ret == 0 && S_ISREG(sbuf.st_mode)) { fprintf(stdout,"Using existing config: %s\n",config); global = load_global(config,noxdcc == 1 ? 0 : 1); } else if(ret == 0 && !S_ISREG(sbuf.st_mode)) { fprintf(stderr,"Error: %s is not a regular file.\n",config); exit(1); } else { fprintf(stdout,"Creating new config: %s\n",config); global = bot_init(); } if(global == NULL) { perror("Error Loading"); exit(1); } if(!nobackground) to_background(); setup_signals(); if(global->settings->config != NULL) xfree(global->settings->config); global->settings->config = xstrdup(config); for(i = 0; i < global->no_users; ++i) user_reload_privs(global->users[i]); if(global->settings->pid != NULL) { pid = getpid(); fh = fopen(global->settings->pid,"w"); if(fh != NULL) { fprintf(fh,"%d",(int)pid); fclose(fh); } } if(nobackground) { global->log_subs.fd = fileno(stderr); //fcntl(global->log_subs.fd,F_SETFL,O_NONBLOCK); } page_size = sysconf(_SC_PAGESIZE); validity_cache_init(); admin_global_init(); pack_cache_fill(); se_global_start(); start_threads(); } static void set_core_limit(void) { struct rlimit lm; if(getrlimit(RLIMIT_CORE,&lm) != 0) { fprintf(stderr,"Unable to get core limit.\n"); return; } if(lm.rlim_cur == RLIM_INFINITY) return; lm.rlim_cur = MIN(lm.rlim_max,CORE_LIMIT); if(setrlimit(RLIMIT_CORE,&lm) != 0) fprintf(stderr,"Unable to set core limit to infinity to %lu.\n", (unsigned long)lm.rlim_cur); } /** usage * Outputs a description of all the bots command line flags, then * terminates the process. */ static void usage(void) { fprintf(stderr, "Usage: arisa [-b] [-t] [-v] [-x] \n\n" " -b\t\tdo not enter background mode\n" " -c\t\tdo not alter the core limit\n" " -t\t\texecute builtin debugging testsuite\n" " -v\t\tdisplay version\n" " -x\t\tdo not restore active sends from config file\n" " -?\t\tthis dialog\n" "\nReport Bugs to .\n" ); exit(0); } /** main * Entry point for the arisa executable. Parses command line arguments * then initiates bot_startup -> bot_main -> bot_shutdown. */ int main(int argc, char *argv[]) { char *config = NULL; int nobackground = 0, noxdcc = 0, noalter_core = 0; int c; for(;;) { c = getopt(argc,argv,"bctvx"); if(c == -1) break; switch(c) { case 'b': nobackground = 1; break; case 'c': noalter_core = 0; break; case 't': testsuite(argc,argv); return 0; case 'v': fprintf(stdout,"%s\n",ARISA_VERSION); return 0; case 'x': noxdcc = 1; break; case '?': usage(); break; } } if(optind < argc) config = argv[optind]; if(config == NULL) usage(); if(!noalter_core) set_core_limit(); bot_startup(config,nobackground,noxdcc); bot_main(NULL); bot_shutdown(); return 0; }