/* * $Id: ramkomd.c,v 0.133 2005/12/18 22:18:14 ceder Exp $ * Copyright (C) 1991-1999, 2001-2005 Lysator Academic Computer Association. * * This file is part of the LysKOM server. * * LysKOM 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 1, or (at your option) * any later version. * * LysKOM 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 LysKOM; see the file COPYING. If not, write to * Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN, * or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, * MA 02139, USA. * * Please report bugs at http://bugzilla.lysator.liu.se/. */ /* * The next comment block is a historic comment, written in Swedish * using ISO 646 since nobody in Lysator knew about ISO 8859-1 by * then. It translates, rougly, to "This is the main program of the * server. It will hopefully be bigger than it is at the moment. * Created by Willför 31-mar-1990." */ /* * Detta {r serverns huvudprogram. Det kommer f|rhoppningsvis bli st|rre * {n det {r just nu... * * Created by Willf|r 31/3-90 * * It has grown! /ceder */ #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_LOCALE_H # include #endif #include #include #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #endif #include #include "timewrap.h" #ifdef HAVE_SYS_RESOURCE_H # include #endif #include #include #if defined(HAVE_SYS_PARAM_H) && !defined(HAVE_GETCWD) # include #endif #include #include #include #include #include #include "oop.h" #include "exp.h" #include "s-string.h" #include "misc-types.h" #include "kom-types.h" #include "com.h" #include "async.h" #include "connections.h" #include "internal-connections.h" #include "kom-errno.h" #include "oop-malloc.h" #include "isc-malloc.h" #include "isc-interface.h" #include "kom-config.h" #include "cache.h" #include "string-malloc.h" #include "lyskomd.h" #include "log.h" #include "server/smalloc.h" #include "kom-memory.h" #include "conf-file.h" #include "param.h" #include "server-config.h" #include "manipulate.h" #include "version-info.h" #include "aux-items.h" #include "admin.h" #include "unused.h" #include "sigflags.h" #include "local-to-global.h" #include "server-time.h" #include "lockdb.h" #include "linkansi.h" #ifdef TRACED_ALLOCATIONS # include "trace-alloc.h" #endif #include "eintr.h" #include "stats.h" #if defined(HAVE_SETRLIMIT) && defined(RLIMIT_OFILE) && !defined(RLIMIT_NOFILE) # define RLIMIT_NOFILE RLIMIT_OFILE #endif #if !HAVE_RLIM_T typedef int rlim_t; #endif #ifndef NDEBUG int buglevel = 0; #endif /* Don't place lyskomd in the background. Write the log to stdout. Prompt for input on stdin before exiting. This is primarily intended for debugging and the test suite. */ static int foreground = 0; static oop_adapter_signal *kom_signal_adapter; static void dump_exit_statistics(void); static oop_call_signal sighandler_term; static oop_call_signal sighandler_quit; static oop_call_signal sighandler_usr1; static oop_call_signal sighandler_usr2; static oop_call_signal sighandler_winch; /* handle_accept_event is declared in connections.h. We redeclare it once more here, just to make sure that its signature matches the isc_accept_callback typedef. */ isc_accept_callback handle_accept_event; #ifdef HAVE_STRUCT_SIGACTION /* Assigning SIG_IGN to sa_handler results in ramkomd.c:310: warning: function declaration isn't a prototype on certain compilers. By performing the assignment in a function the number of warnings are reduced. */ static inline void set_handler_sig_ign(struct sigaction *ptr) { ptr->sa_handler = SIG_IGN; } #endif static isc_write_queue_change_cb write_queue_change_callback; static void write_queue_change_callback(int delta) { update_stat(STAT_SEND_QUEUE, delta); } static void start_listen_sockets(void) { struct isc_scb *listen_client; int i; for (i = 0; i < param.listen.size; ++i) { struct ipport_entry *port = ¶m.listen.entries[i]; listen_client = isc_listentcp(kom_server_mcb, port->ipaddr, port->port, handle_accept_event); if (listen_client == NULL) restart_kom("server_init: can't isc_listentcp(%s, %s)\n", port->ipaddr ? port->ipaddr : "*", port->port); assert(listen_client->laddr != NULL); kom_log("Listening for clients on %s:%d.\n", isc_getipnum(listen_client->laddr, NULL, 0), isc_getportnum(listen_client->laddr)); } } static void server_init(void) { oop_source *src; #ifdef HAVE_STRUCT_SIGACTION struct sigaction act; #endif oop_malloc = &oop_malloc_wrapper; oop_realloc = &oop_realloc_wrapper; oop_free = &oop_free_wrapper; kom_server_oop_src = oop_sys_new(); if (kom_server_oop_src == NULL) restart_kom("server_init: can't get system event source\n"); kom_signal_adapter = oop_signal_new(oop_sys_source(kom_server_oop_src)); if (kom_signal_adapter == NULL) restart_kom("server_init: can't create signal adapter\n"); /* Ignore the signals before we register them with liboop. That way, when liboop reinstalls the old signal handler during shutdown, we will ignore them rather than stop prematurely during the shutdown. This is especially important for the SIGTERM signal, which is sent periodically by updateLysKOM. */ #ifdef HAVE_STRUCT_SIGACTION sigemptyset(&act.sa_mask); act.sa_flags = 0; set_handler_sig_ign(&act); sigaction(SIGHUP, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGINT, &act, NULL); sigaction(SIGQUIT, &act, NULL); sigaction(SIGUSR1, &act, NULL); sigaction(SIGUSR2, &act, NULL); sigaction(SIGWINCH, &act, NULL); #else signal(SIGHUP, SIG_IGN); signal(SIGTERM, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGUSR1, SIG_IGN); signal(SIGUSR2, SIG_IGN); signal(SIGWINCH, SIG_IGN); #endif src = oop_signal_source(kom_signal_adapter); oop_signal_use_sa_restart(); src->on_signal(src, SIGHUP, sighandler_term, NULL); src->on_signal(src, SIGTERM, sighandler_term, NULL); src->on_signal(src, SIGINT, sighandler_term, NULL); src->on_signal(src, SIGQUIT, sighandler_quit, NULL); src->on_signal(src, SIGUSR1, sighandler_usr1, NULL); src->on_signal(src, SIGUSR2, sighandler_usr2, NULL); src->on_signal(src, SIGWINCH, sighandler_winch, NULL); /* ** Setup some parameters here */ isc_setallocfn(&isc_malloc_wrapper, &isc_realloc_wrapper, &isc_free_wrapper); kom_server_mcb = isc_initialize(oop_sys_source(kom_server_oop_src), write_queue_change_callback); if ( kom_server_mcb == NULL ) restart_kom("server_init: can't isc_initialize()\n"); isc_cfg_fd_relocate(kom_server_mcb, PROTECTED_FDS); isc_cfg_stale_timeout(kom_server_mcb, param.stale_timeout, param.connect_timeout); isc_cfg_queue_size(kom_server_mcb, param.maxqueuedsize_bytes, param.maxmsgsize, param.maxqueuedsize, param.maxdequeuelen); start_listen_sockets(); /* * Ignore SIGPIPE, which the server gets if it tries to write to a * socket and the client has died. The server will anyhow handle * this situation correct. */ #ifdef HAVE_STRUCT_SIGACTION sigemptyset(&act.sa_mask); act.sa_flags = 0; set_handler_sig_ign(&act); sigaction(SIGPIPE, &act, NULL); #else signal(SIGPIPE, SIG_IGN); #endif } static void init_data_base(void) { kom_log("Database = %s\n", param.datafile_name); kom_log("Backup = %s\n", param.backupfile_name); kom_log("2nd Backup = %s\n", param.backupfile_name_2); kom_log("Lock File = %s\n", param.lockfile_name); if ( init_cache() == FAILURE ) restart_kom ("Cannot find database.\n"); } static void * sighandler_term(oop_source *UNUSED(source), int sig, void *UNUSED(user)) { if (sig == SIGTERM) kom_log("Signal TERM received. Shutting down server.\n"); else if (sig == SIGHUP) kom_log("Signal HUP received. Shutting down server." " Please use SIGTERM instead.\n"); else if (sig == SIGINT) kom_log("Signal INT received. Shutting down server." " Please use SIGTERM instead.\n"); else kom_log("Some signal received. Shutting down server." " Please use SIGTERM instead.\n"); go_and_die = TRUE; return OOP_HALT; } static void * sighandler_quit(oop_source *UNUSED(source), int UNUSED(sig), void *UNUSED(user)) { kom_log ("Signal QUIT received - syncing...\n"); cache_sync_all(); kom_log ("Dumping core now.\n"); abort(); } static void * sighandler_usr1(oop_source *UNUSED(source), int UNUSED(sig), void *UNUSED(user)) { dump_statistics(); return OOP_CONTINUE; } static void * sighandler_usr2(oop_source *UNUSED(source), int UNUSED(sig), void *UNUSED(user)) { int child; kom_log ("Signal USR2 received - will dump core now. (Check that child dies.)\n"); if ((child = fork()) == 0) { abort(); kom_log ("Abort() failed!!!\n"); exit(1); } else if (child < 0) { kom_log ("Couldn't fork.\n"); } else { wait (NULL); } return OOP_CONTINUE; } static void * sighandler_winch(oop_source *UNUSED(source), int UNUSED(sig), void *UNUSED(user)) { free_aux_item_definitions(); initialize_aux_items(param.aux_def_file); kom_log("Signal WINCH received. aux definitions reloaded.\n"); return OOP_CONTINUE; } static void save_pid(void) { FILE *fp; if ((fp = i_fopen(param.pid_name, "w")) == NULL) return; fprintf(fp, "%ld\n", (long)getpid()); i_fclose(fp); } static void go_daemon(void) { pid_t child; int fd; #ifdef HAVE_STRUCT_SIGACTION struct sigaction act; #endif if (foreground != 0) { return; } if (getppid() != 1) { /* We were not invoked from /etc/inittab, so disassociate from controlling terminal. */ #ifdef HAVE_STRUCT_SIGACTION sigemptyset(&act.sa_mask); act.sa_flags = 0; set_handler_sig_ign(&act); # ifdef SIGTTOU sigaction(SIGTTOU, &act, NULL); # endif # ifdef SIGTTIN sigaction(SIGTTIN, &act, NULL); # endif # ifdef SIGTSTP sigaction(SIGTSTP, &act, NULL); # endif #else /* !HAVE_STRUCT_SIGACTION */ # ifdef SIGTTOU signal(SIGTTOU, SIG_IGN); # endif # ifdef SIGTTIN signal(SIGTTIN, SIG_IGN); # endif # ifdef SIGTSTP signal(SIGTSTP, SIG_IGN); # endif #endif child = fork(); if (child < 0) restart_kom("fork failed: %d\n", errno); else if (child > 0) exit (0); /* parent */ setsid(); } /* Close all file descriptors */ for (fd = 0; fd < fd_ceiling; fd++) close(fd); if (open("/dev/null", O_RDONLY) != 0 ||open("/dev/null", O_WRONLY) != 1 ||open(param.logfile_name, O_WRONLY|O_CREAT|O_APPEND, 0644) != 2) { /* Kinda stupid to try to log an error message now, but who knows? */ restart_kom("open of log file failed: %d\n", errno); } kom_log("*** Version %s (process %lu) coming up.\n", kom_version_info.server_version, (unsigned long)getpid()); } static void initialize(const char *config_file) { #ifdef USING_RLIMIT_NOFILE struct rlimit rlim; #endif read_configuration(config_file); initialize_aux_items(param.aux_def_file); #ifdef HAVE_LOCALE_H if (param.use_locale != NULL) if (setlocale(LC_CTYPE, param.use_locale) == NULL) { fprintf(stderr, "setlocale: "); perror(param.use_locale); exit(1); } #else if (param.use_locale != NULL) { fprintf(stderr, "locale not supported in your environment.\n"); exit(1); } #endif if (param.no_files != -1 && param.no_files > FD_SETSIZE) { fprintf(stderr, "The \"Open files: %ld\" is too large: FD_SETSIZE is %ld\n", (long)param.no_files, (long)FD_SETSIZE); exit(1); } #ifdef USING_RLIMIT_NOFILE if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) { perror("getrlimit(RLIMIT_NOFILE) failed"); exit(1); } if (param.no_files != -1) { if ((rlim_t)param.no_files > rlim.rlim_max) { fprintf(stderr, "attempt to raise open files from %ld to %ld, " "but only %ld is allowed\n", (long)rlim.rlim_cur, (long)param.no_files, (long)rlim.rlim_max); rlim.rlim_cur = rlim.rlim_max; } else rlim.rlim_cur = param.no_files; if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) { perror("setrlimit failed"); exit(1); } if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) { perror("getrlimit(RLIMIT_NOFILE) failed"); exit(1); } if (rlim.rlim_cur != (rlim_t)param.no_files) restart_kom("getrlimit after setrlimit returns %ld, not %ld.\n", (long)rlim.rlim_cur, (long)param.no_files); } fd_ceiling = rlim.rlim_cur; #elif HAVE_GETDTABLESIZE if (param.no_files == -1) fd_ceiling = getdtablesize(); else if (param.no_files >= getdtablesize()) { fprintf(stderr, "getdtablesize() indicates that at most %ld files" " can be used.\n", (long)getdtablesize()); exit(1); } else fd_ceiling = param.no_files; #elif defined(OPEN_MAX) if (param.no_files == -1) fd_ceiling = OPEN_MAX; else if (param.no_files >= OPEN_MAX) { fprintf(stderr, "OPEN_MAX indicates that at most %ld files" " can be used.\n", (long)OPEN_MAX); exit(1); } else fd_ceiling = param.no_files; #else # error Do not know how to find maximum number of open files. #endif if (fd_ceiling > FD_SETSIZE) fd_ceiling = FD_SETSIZE; go_daemon(); if (lock_db() < 0) { /* Don't actually die until something is entered on stdin in debug mode. This is mainly here for the benefit of the test suite, but is could also be useful to be able to attach a debugger and do pre-mortem debugging of the process at this point. */ kom_log("Cannot obtain database lock. Exiting.\n"); if (foreground) { kom_log("Press enter to terminate lyskomd\n"); getchar(); } exit(1); } server_init(); init_data_base(); } /* Stop complaint from gcc 2.0 about "no previous prototype for `main'". */ int main(int argc, char **argv); int main (int argc, char **argv) { int i; const char *config_file; oop_source *src; link_ansi(); set_initial_time(); #ifdef TRACED_ALLOCATIONS /* We must do this before we allocate any memory... */ { char buf[1024]; char *nl; fputs("Where does the trace want to go today? [stderr]\n", stdout); fflush(stdout); if (fgets(buf, sizeof(buf), stdin) != buf) restart_kom("main(): unable to read trace location\n"); if ((nl = strchr(buf, '\n')) != NULL) *nl = '\0'; trace_alloc_file(buf); } #endif kom_log("*** Version %s (process %lu) started.\n", kom_version_info.server_version, (unsigned long)getpid()); #ifdef DEBUG_CALLS kom_log("WARNING: This server was compiled with --with-debug-calls.\n"); kom_log("It isn't safe to use in a production environment.\n"); #else kom_log("Debug calls are disabled, as they should be.\n"); #endif #ifdef ENCRYPT_PASSWORDS /* Seed the random number generator. */ /* FIXME (bug 1068): This is not a good way to seed it... */ srand(time(NULL) + getpid()); #endif /* Initialize the string handling package. */ s_set_storage_management(string_malloc, string_realloc, string_free); /* Parse command line arguments. */ for (i = 1; i < argc && argv[i][0] == '-'; i++) switch (argv[i][1]) { case 'd': buglevel++; break; case 'f': foreground = 1; break; default: restart_kom("usage: %s [-f] [-d ...] [config-file]\n", argv[0]); } /* Read in the configuration file. */ if (i < argc) config_file = argv[i++]; else config_file = get_default_config_file_name(); if (i < argc) restart_kom("usage: %s [-d ...] [config-file]\n", argv[0]); init_stats(); initialize(config_file); /* Read config, listen, and start db */ chdir(param.core_dir); save_pid(); toploop(); /* There is no use sending logout messages to all sessions. */ param.send_async_messages = FALSE; logout_all_clients(); isc_shutdown(kom_server_mcb); cache_sync_all(); unlock_db(); src = oop_signal_source(kom_signal_adapter); src->cancel_signal(src, SIGWINCH, sighandler_winch, NULL); src->cancel_signal(src, SIGUSR2, sighandler_usr2, NULL); src->cancel_signal(src, SIGUSR1, sighandler_usr1, NULL); src->cancel_signal(src, SIGQUIT, sighandler_quit, NULL); src->cancel_signal(src, SIGHUP, sighandler_term, NULL); src->cancel_signal(src, SIGTERM, sighandler_term, NULL); src->cancel_signal(src, SIGINT, sighandler_term, NULL); oop_signal_delete(kom_signal_adapter); oop_sys_delete(kom_server_oop_src); /* Finish */ dump_exit_statistics(); kom_log("%s terminated normally.\n", argv[0]); /* Don't actually die until something is entered on stdin in debug mode. This is mainly here for the benefit of the test suite, but is could also be useful to be able to attach a debugger and do pre-mortem debugging of the process at this point. */ if (foreground) { kom_log("Press enter to terminate lyskomd\n"); getchar(); } exit(0); } static void dump_exit_statistics(void) { FILE *stat_file; time_t now; time(&now); stat_file = i_fopen(param.memuse_name, "a"); if ( stat_file == NULL ) restart_kom("Can't open file to save memory usage to.\n"); fprintf(stat_file, "\nLysKOM Server going down at %s\n", ctime(&now)); dump_cache_stats (stat_file); free_all_tmp(); free_all_cache(); free_all_jubel(); clear_info(&kom_info); free_aux_item_definitions(); free_configuration(); free_default_config_file_name(); dump_smalloc_counts(stat_file); dump_alloc_counts(stat_file); dump_cache_mem_usage(stat_file); dump_string_alloc_counts(stat_file); dump_allocated_connections(stat_file); dump_isc_alloc_counts(stat_file); dump_oop_alloc_counts(stat_file); dump_l2g_stats(stat_file); i_fclose (stat_file); }