/* * Copyright (C) 1997-2007 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file server.c Paraslash's main server */ /** \mainpage Paraslash API Reference * * Good starting points for reading are probably \ref audio_file_selector, * \ref sender, \ref receiver, \ref receiver_node, \ref filter, \ref * filter_node. * */ #include "server.cmdline.h" #include "afs_common.h" #include "server.h" #include "vss.h" #include "config.h" #include "close_on_fork.h" #include "send.h" #include "error.h" #include "net.h" #include "daemon.h" #include "string.h" #include "ipc.h" #include "fd.h" #include "signal.h" #include "user_list.h" /** define the array of error lists needed by para_server */ INIT_SERVER_ERRLISTS; /** shut down non-authorized connections after that many seconds */ #define ALARM_TIMEOUT 10 /** * pointer to shared memory area for communication between para_server * and its children. exported to vss.c. command.c and to all selectors. */ struct misc_meta_data *mmd; /** * the configuration of para_server * * It also contains the options for all audio file selectors, audio format handler * and all supported senders. */ struct server_args_info conf; /** the file containing user information (public key, permissions) */ char *user_list_file = NULL; extern void dccp_send_init(struct sender *); extern void http_send_init(struct sender *); extern void ortp_send_init(struct sender *); /* TODO: This is better handled by autoconf */ /** the list of supported audio file selectors */ struct audio_file_selector selectors[] = { { .name = "random", .init = random_selector_init, .update_audio_file = NULL, }, { .name = "playlist", .init = playlist_selector_init, .update_audio_file = NULL, .pre_select = NULL, .post_select = NULL, }, #ifdef HAVE_MYSQL { .name = "mysql", .init = mysql_selector_init, .update_audio_file = NULL, .pre_select = NULL, .post_select = NULL, }, #endif { .name = NULL, } }; /** the list of supported senders */ struct sender senders[] = { { .name = "http", .init = http_send_init, }, { .name = "dccp", .init = dccp_send_init, }, #ifdef HAVE_ORTP { .name = "ortp", .init = ortp_send_init, }, #endif { .name = NULL, } }; /* global variables for server-internal use */ static FILE *logfile; static int mmd_mutex, mmd_shm_id; static int signal_pipe; /** * para_server's log function * * \param ll the log level * \param fmt the format string describing the log message */ void para_log(int ll, const char* fmt,...) { va_list argp; FILE *outfd; struct tm *tm; time_t t1; char str[MAXLINE] = ""; pid_t mypid; if (ll < conf.loglevel_arg) return; outfd = logfile? logfile : stderr; time(&t1); tm = localtime(&t1); strftime(str, MAXLINE, "%b %d %H:%M:%S", tm); fprintf(outfd, "%s ", str); if (conf.loglevel_arg <= INFO) fprintf(outfd, "%i: ", ll); mypid = getpid(); if (conf.loglevel_arg <= INFO) fprintf(outfd, "(%d) ", mypid); va_start(argp, fmt); vfprintf(outfd, fmt, argp); va_end(argp); } /* * setup shared memory area and get mutex for locking */ static void shm_init(void) { void *shm; int ret = shm_new(sizeof(struct misc_meta_data)); if (ret < 0) goto err_out; ret = shm_attach(ret, ATTACH_RW, &shm); if (ret < 0) goto err_out; mmd = shm; mmd_shm_id = ret; ret = mutex_new(); if (ret < 0) goto err_out; mmd_mutex = ret; mmd->selector_num = 0; mmd->num_played = 0; mmd->num_commands = 0; mmd->events = 0; mmd->num_connects = 0; mmd->active_connections = 0; strcpy(mmd->filename, "(none)"); mmd->audio_format = -1; mmd->vss_status_flags = VSS_NEXT; mmd->new_vss_status_flags = VSS_NEXT; mmd->sender_cmd_data.cmd_num = -1; return; err_out: PARA_EMERG_LOG("%s", PARA_STRERROR(-ret)); exit(EXIT_FAILURE); } /** * lock the shared memory area containing the mmd struct * * \sa semop(2), struct misc_meta_data */ void mmd_lock(void) { mutex_lock(mmd_mutex); } /** * unlock the shared memory area containing the mmd struct * * \sa semop(2), struct misc_meta_data */ void mmd_unlock(void) { mutex_unlock(mmd_mutex); } static void parse_config(int override) { char *home = para_homedir(); struct stat statbuf; int ret; char *cf; if (conf.config_file_given) cf = para_strdup(conf.config_file_arg); else cf = make_message("%s/.paraslash/server.conf", home); free(user_list_file); if (!conf.user_list_given) user_list_file = make_message("%s/.paraslash/server.users", home); else user_list_file = para_strdup(conf.user_list_arg); ret = stat(cf, &statbuf); if (ret && conf.config_file_given) { ret = -1; PARA_EMERG_LOG("can not stat config file %s\n", cf); goto out; } if (!ret) { int tmp = conf.daemon_given; struct server_cmdline_parser_params params = { .override = override, .initialize = 0, .check_required = 0, .check_ambiguity = 0 }; server_cmdline_parser_config_file(cf, &conf, ¶ms); conf.daemon_given = tmp; } /* logfile */ if (!conf.logfile_given && conf.daemon_given) { ret = -1; PARA_EMERG_LOG("%s", "daemon, but no log file\n"); goto out; } if (conf.logfile_given) logfile = open_log(conf.logfile_arg); ret = 1; out: free(cf); free(home); if (ret > 0) return; free(user_list_file); user_list_file = NULL; exit(EXIT_FAILURE); } static void setup_signal_handling(void) { int ret = 0; signal_pipe = para_signal_init(); PARA_NOTICE_LOG("%s", "setting up signal handlers\n"); ret += para_install_sighandler(SIGINT); ret += para_install_sighandler(SIGTERM); ret += para_install_sighandler(SIGHUP); ret += para_install_sighandler(SIGCHLD); ret += para_install_sighandler(SIGUSR1); signal(SIGPIPE, SIG_IGN); if (ret != 5) { PARA_EMERG_LOG("%s", "could not install signal handlers\n"); exit(EXIT_FAILURE); } } static void init_selector(void) { int i, ret; mmd->selector_change = -1; /* no change nec., set to new num by com_chs */ if (!conf.selector_given) goto random; for (i = 0; selectors[i].name; i++) { if (strcmp(selectors[i].name, conf.selector_arg)) continue; PARA_NOTICE_LOG("initializing %s audio file selector\n", selectors[i].name); ret = selectors[i].init(&selectors[i]); if (ret < 0) { PARA_WARNING_LOG("%s", PARA_STRERROR(-ret)); break; } mmd->selector_num = i; return; } PARA_WARNING_LOG("%s", "falling back to the random selector\n"); random: mmd->selector_num = 0; selectors[0].init(&selectors[0]); /* always successful */ } static unsigned init_network(void) { int fd, ret = init_tcp_socket(conf.port_arg); if (ret < 0) goto err; fd = ret; ret = mark_fd_nonblock(fd); if (ret < 0) goto err; return fd; err: PARA_EMERG_LOG("%s\n", PARA_STRERROR(-ret)); exit(EXIT_FAILURE); } static void init_random_seed(void) { int fd, ret = -1; unsigned int seed; size_t len = sizeof(unsigned int); fd = open("/dev/urandom", O_RDONLY); if (fd < 0) goto out; ret = -2; if (read(fd, &seed, len) != len) goto out; srandom(seed); ret = 1; out: if (fd >= 0) close(fd); if (ret > 0) return; PARA_EMERG_LOG("can not seed pseudo random generator (ret = %d)\n", ret); exit(EXIT_FAILURE); } static unsigned do_inits(int argc, char **argv) { /* connector's address information */ int sockfd; init_random_seed(); /* parse command line options */ server_cmdline_parser(argc, argv, &conf); HANDLE_VERSION_FLAG("server", conf); para_drop_privileges(conf.user_arg, conf.group_arg); /* parse config file, open log and set defaults */ parse_config(0); log_welcome("para_server", conf.loglevel_arg); shm_init(); /* init mmd struct */ server_uptime(UPTIME_SET); /* reset server uptime */ init_user_list(user_list_file); /* become daemon */ if (conf.daemon_given) daemon_init(); init_selector(); // PARA_ERROR_LOG("num: %d\n", mmd->selector_num); PARA_NOTICE_LOG("%s", "initializing virtual streaming system\n"); vss_init(); mmd->server_pid = getpid(); setup_signal_handling(); mmd_lock(); /* init network socket */ PARA_NOTICE_LOG("%s", "initializing tcp command socket\n"); sockfd = init_network(); PARA_NOTICE_LOG("%s", "init complete\n"); return sockfd; } static void change_selector(void) { int ret, old = mmd->selector_num, new = mmd->selector_change; selectors[old].shutdown(); ret = selectors[new].init(&selectors[new]); mmd->selector_change = -1; /* reset */ if (ret >= 0) { mmd->selector_num = new; return; } /* init failed */ PARA_ERROR_LOG("%s -- switching to the random selector\n", PARA_STRERROR(-ret)); selectors[0].init(&selectors[0]); mmd->selector_num = 0; } /* * called when server gets SIGHUP or when client invokes hup command. */ static void handle_sighup(void) { PARA_NOTICE_LOG("%s", "SIGHUP\n"); close_log(logfile); /* gets reopened if necessary by parse_config */ logfile = NULL; parse_config(1); /* reopens log */ mmd->selector_change = mmd->selector_num; /* do not change selector.. */ change_selector(); /* .. just reload */ init_user_list(user_list_file); /* reload user list */ } static void status_refresh(void) { static int prev_uptime = -1, prev_events = -1; int uptime = server_uptime(UPTIME_GET), ret = 1; if (prev_events != mmd->events) goto out; if (mmd->new_vss_status_flags != mmd->vss_status_flags) goto out; if (uptime / 60 != prev_uptime / 60) goto out; ret = 0; out: prev_uptime = uptime; prev_events = mmd->events; mmd->vss_status_flags = mmd->new_vss_status_flags; if (ret) { PARA_DEBUG_LOG("%d events, forcing status update, af = %d\n", mmd->events, mmd->audio_format); killpg(0, SIGUSR1); } } /** * the main function of para_server * * \param argc usual argument count * \param argv usual argument vector * * \return EXIT_SUCCESS or EXIT_FAILURE * */ int main(int argc, char *argv[]) { /* listen on sock_fd, new connection on new_fd */ int sockfd, new_fd; struct sockaddr_in their_addr; int i, max_fileno, ret; pid_t chld_pid; fd_set rfds, wfds; struct timeval *timeout; valid_fd_012(); sockfd = do_inits(argc, argv); repeat: FD_ZERO(&rfds); FD_ZERO(&wfds); max_fileno = -1; /* check socket and signal pipe in any case */ para_fd_set(sockfd, &rfds, &max_fileno); para_fd_set(signal_pipe, &rfds, &max_fileno); timeout = vss_preselect(); status_refresh(); for (i = 0; senders[i].name; i++) { if (senders[i].status != SENDER_ON) continue; if (!senders[i].pre_select) continue; senders[i].pre_select( &max_fileno, &rfds, &wfds); } if (selectors[mmd->selector_num].pre_select) { ret = selectors[mmd->selector_num].pre_select(&rfds, &wfds); max_fileno = PARA_MAX(max_fileno, ret); } mmd_unlock(); ret = para_select(max_fileno + 1, &rfds, &wfds, timeout); mmd_lock(); if (mmd->selector_change >= 0) change_selector(); if (selectors[mmd->selector_num].post_select) selectors[mmd->selector_num].post_select(&rfds, &wfds); if (ret < 0) goto repeat; for (i = 0; senders[i].name; i++) { if (senders[i].status != SENDER_ON) continue; if (!senders[i].post_select) continue; senders[i].post_select(&rfds, &wfds); } vss_send_chunk(); status_refresh(); if (FD_ISSET(signal_pipe, &rfds)) { int sig; sig = para_next_signal(); switch (sig) { case SIGHUP: handle_sighup(); break; case SIGCHLD: para_reap_children(); break; /* die on sigint/sigterm. Kill all children too. */ case SIGINT: case SIGTERM: PARA_EMERG_LOG("terminating on signal %d\n", sig); kill(0, SIGTERM); selectors[mmd->selector_num].shutdown(); mutex_destroy(mmd_mutex); shm_detach(mmd); shm_destroy(mmd_shm_id); exit(EXIT_FAILURE); } } if (mmd->sender_cmd_data.cmd_num >= 0) { int num = mmd->sender_cmd_data.cmd_num, s = mmd->sender_cmd_data.sender_num; if (senders[s].client_cmds[num]) senders[s].client_cmds[num](&mmd->sender_cmd_data); mmd->sender_cmd_data.cmd_num = -1; } if (!FD_ISSET(sockfd, &rfds)) goto repeat; new_fd = para_accept(sockfd, &their_addr, sizeof(struct sockaddr_in)); if (new_fd < 0) goto repeat; PARA_INFO_LOG("got connection from %s, forking\n", inet_ntoa(their_addr.sin_addr)); mmd->num_connects++; mmd->active_connections++; random(); chld_pid = fork(); if (chld_pid < 0) { PARA_CRIT_LOG("%s", "fork failed\n"); goto repeat; } if (chld_pid) { close(new_fd); /* parent keeps accepting connections */ goto repeat; } alarm(ALARM_TIMEOUT); close_listed_fds(); close(sockfd); /* child doesn't need the listener */ /* * put info on who we are serving into argv[0] to make * client ip visible in top/ps */ for (i = argc - 1; i >= 0; i--) memset(argv[i], 0, strlen(argv[i])); sprintf(argv[0], "para_server (serving %s)", inet_ntoa(their_addr.sin_addr)); return handle_connect(new_fd, &their_addr); }