From: Andre Noll Date: Sun, 20 May 2018 09:26:26 +0000 (+0200) Subject: Merge branch 'refs/heads/t/clean_server_exit' X-Git-Tag: v0.6.2~4 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=e2167286448ce2ed9a01a548e7e9832563035088;hp=3998a8c581623224b7b56bce593646b2c8516a0f Merge branch 'refs/heads/t/clean_server_exit' This series removes many memory leaks of para_server by refactoring the shutdown and signal handling code. Most of the leaks happen only at shutdown and are hence harmless. But it is still good to plug the leaks because this puts more focus on real memory leaks in the valgrind output. The merge conflicted rather badly due to the changes introduced with the crypt branch that was merged last week. The resolution has been thoroughly tested, though. * refs/heads/t/clean_server_exit: (32 commits) command.c: Document return value of handle_connect(). user_list: Make list head static. afs: Allow database switching on sighup. afs: Free current mood or playlist on exit. afs: Free status items on exit. afs: Shutdown signals on exit. server: Free parse result also in afs. afs: Deplete user list at startup. server: Free audio file header on exit. sender: Deplete ACLs on exit. Remove some unused includes from {dccp,http}_send.c. server: Make argument of user_list_init() constant. server: Deplete user list on exit. server: Combine user_list_init() and populate(). server: Move para_fgets() to user_list.c. server: Initialize user list at compile time. server: Rename functions related to user lists. server: Constify return value of lookup_user(). server: Let stat command handler perform cleanup on signals. server: Have afs process close the current mood on exit(). ... --- diff --git a/Makefile.real b/Makefile.real index 76487677..dae48f0e 100644 --- a/Makefile.real +++ b/Makefile.real @@ -144,6 +144,7 @@ cc-option = $(shell \ ) STRICT_CFLAGS += $(call cc-option, -Wformat-signedness) +STRICT_CFLAGS += $(call cc-option, -Wdiscarded-qualifiers) # To put more focus on warnings, be less verbose as default # Use 'make V=1' to see the full commands diff --git a/NEWS.md b/NEWS.md index 11f275da..d193a1a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,14 @@ NEWS the geometry of the terminal changes. - Minor documentation improvements. - Improvements to the crypto subsystem. +- The server subcommand "task" has been deprecated. It still works, + but prints nothing. It will be removed in the next major release. +- Server log output is now serialized, avoiding issues with partial + lines. +- It is now possible to switch to a different afs database by changing + the server configuration and sending SIGHUP to the server process. + +Download: [tarball](./releases/paraslash-git.tar.xz) ---------------------------------------- 0.6.1 (2017-09-23) "segmented iteration" diff --git a/acl.c b/acl.c index 10f56bf1..2c900526 100644 --- a/acl.c +++ b/acl.c @@ -31,10 +31,12 @@ struct access_info { /** * Return true if addr_1 matches addr_2 in the first `netmask' bits. */ -static int v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask) +static bool v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask) { uint32_t mask = ~0U; + if (netmask == 0) /* avoid 32-bit shift, which is undefined in C. */ + return true; if (netmask < 32) mask <<= (32 - netmask); return (htonl(addr_1) & mask) == (htonl(addr_2) & mask); @@ -99,14 +101,18 @@ static void acl_del_entry(struct list_head *acl, char *addr, unsigned netmask) struct access_info *ai, *tmp; struct in_addr to_delete; + PARA_NOTICE_LOG("removing entries matching %s/%u\n", addr, netmask); inet_pton(AF_INET, addr, &to_delete); list_for_each_entry_safe(ai, tmp, acl, node) { - if (v4_addr_match(to_delete.s_addr, ai->addr.s_addr, PARA_MIN(netmask, ai->netmask))) { - PARA_NOTICE_LOG("removing %s/%u from access list\n", - addr, ai->netmask); + char dst[INET_ADDRSTRLEN + 1]; + const char *p = inet_ntop(AF_INET, &ai->addr.s_addr, + dst, sizeof(dst)); + if (p) + PARA_INFO_LOG("removing %s/%u\n", p, + ai->netmask); list_del(&ai->node); free(ai); } diff --git a/afs.c b/afs.c index 538606ad..4fe2140b 100644 --- a/afs.c +++ b/afs.c @@ -638,16 +638,18 @@ static int setup_command_socket_or_die(void) return socket_fd; } +static char *database_dir; + static void close_afs_tables(void) { int i; PARA_NOTICE_LOG("closing afs_tables\n"); for (i = 0; i < NUM_AFS_TABLES; i++) afs_tables[i].close(); + free(database_dir); + database_dir = NULL; } -static char *database_dir; - static void get_database_dir(void) { if (!database_dir) { @@ -1002,9 +1004,14 @@ __noreturn void afs_init(int socket_fd) } ret = schedule(&s); sched_shutdown(&s); + close_current_mood(); out_close: close_afs_tables(); out: + signal_shutdown(signal_task); + free_status_items(); + free(current_mop); + free_lpr(); if (ret < 0) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); @@ -1016,6 +1023,7 @@ static int com_init_callback(struct afs_callback_arg *aca) int i, ret; close_afs_tables(); + get_database_dir(); for (i = 0; i < NUM_AFS_TABLES; i++) { struct afs_table *t = &afs_tables[i]; diff --git a/afs.h b/afs.h index 8beca5ae..b0d283f6 100644 --- a/afs.h +++ b/afs.h @@ -262,6 +262,7 @@ int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi); int get_audio_file_path_of_row(const struct osl_row *row, char **path); int audio_file_loop(void *private_data, osl_rbtree_loop_func *func); int aft_check_callback(struct afs_callback_arg *aca); +void free_status_items(void); /* playlist */ int playlist_open(const char *name); diff --git a/aft.c b/aft.c index 8969e7ac..15769d55 100644 --- a/aft.c +++ b/aft.c @@ -982,6 +982,12 @@ out: WRITE_STATUS_ITEM(pb, SI_file_size, "%ld\n", statbuf.st_size / 1024); } +void free_status_items(void) +{ + freep(&status_items); + freep(&parser_friendly_status_items); +} + static int make_status_items(void) { const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); @@ -991,6 +997,7 @@ static int make_status_items(void) time_t current_time; int ret; + free_status_items(); if (!status_item_ls_data.path) /* no audio file open */ return 0; ret = lls_parse(ARRAY_SIZE(argv), argv, cmd, &opts.lpr, NULL); @@ -1000,23 +1007,20 @@ static int make_status_items(void) if (ret < 0) goto out; make_inode_status_items(&pb); - free(status_items); status_items = pb.buf; memset(&pb, 0, sizeof(pb)); pb.max_size = shm_get_shmmax() - 1; pb.flags = PBF_SIZE_PREFIX; ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time); - if (ret < 0) { - free(status_items); - status_items = NULL; - return ret; - } + if (ret < 0) + goto out; make_inode_status_items(&pb); - free(parser_friendly_status_items); parser_friendly_status_items = pb.buf; ret = 1; out: + if (ret < 0) + free_status_items(); lls_free_parse_result(opts.lpr, cmd); return ret; } diff --git a/close_on_fork.c b/close_on_fork.c index 7e0c8e64..28c5eabb 100644 --- a/close_on_fork.c +++ b/close_on_fork.c @@ -62,13 +62,7 @@ void del_close_on_fork_list(int fd) } } -/** - * Close all fds in the list and destroy all list entries. - * - * This function calls close(3) for each fd in the close-on-fork list - * and empties the list afterwards. - */ -void close_listed_fds(void) +static void deplete_cof_list(bool close_fds) { struct close_on_fork *cof, *tmp; @@ -76,8 +70,32 @@ void close_listed_fds(void) return; list_for_each_entry_safe(cof, tmp, &close_on_fork_list, node) { PARA_DEBUG_LOG("closing fd %d\n", cof->fd); - close(cof->fd); + if (close_fds) + close(cof->fd); list_del(&cof->node); free(cof); } } + +/** + * Close all fds in the list and destroy all list entries. + * + * This function calls close(3) for each fd in the close-on-fork list + * and empties the list afterwards. + * + * \sa \ref deplete_close_on_fork_list(). + */ +void close_listed_fds(void) +{ + deplete_cof_list(true); +} + +/** + * Remove all listed fds from the close on fork list. + * + * This is like \ref close_listed_fds() but does not close the fds. + */ +void deplete_close_on_fork_list(void) +{ + deplete_cof_list(false); +} diff --git a/close_on_fork.h b/close_on_fork.h index 1bd0cd15..0dcc2b6c 100644 --- a/close_on_fork.h +++ b/close_on_fork.h @@ -2,3 +2,4 @@ void del_close_on_fork_list(int fd); void add_close_on_fork_list(int fd); void close_listed_fds(void); +void deplete_close_on_fork_list(void); diff --git a/command.c b/command.c index bfce809a..25b92715 100644 --- a/command.c +++ b/command.c @@ -48,11 +48,15 @@ static const char * const server_command_perms_txt[] = {LSG_SERVER_CMD_AUX_INFOS extern int mmd_mutex; extern struct misc_meta_data *mmd; -extern struct sender senders[]; int send_afs_status(struct command_context *cc, int parser_friendly); +static bool subcmd_should_die; -static void dummy(__a_unused int s) +static void command_handler_sighandler(int s) { + if (s != SIGTERM) + return; + PARA_EMERG_LOG("terminating on signal %d\n", SIGTERM); + subcmd_should_die = true; } /* @@ -235,10 +239,10 @@ static int check_sender_args(struct command_context *cc, return ret; } arg = lls_input(0, lpr); - for (i = 0; senders[i].name; i++) - if (!strcmp(senders[i].name, arg)) + FOR_EACH_SENDER(i) + if (strcmp(senders[i]->name, arg) == 0) break; - if (!senders[i].name) + if (!senders[i]) return -E_COMMAND_SYNTAX; scd->sender_num = i; arg = lls_input(1, lpr); @@ -248,7 +252,7 @@ static int check_sender_args(struct command_context *cc, if (i == NUM_SENDER_CMDS) return -E_COMMAND_SYNTAX; scd->cmd_num = i; - if (!senders[scd->sender_num].client_cmds[scd->cmd_num]) + if (!senders[scd->sender_num]->client_cmds[scd->cmd_num]) return -E_SENDER_CMD; switch (scd->cmd_num) { case SENDER_on: @@ -330,10 +334,10 @@ static int com_sender(struct command_context *cc, struct lls_parse_result *lpr) struct sender_command_data scd; if (lls_num_inputs(lpr) == 0) { - for (i = 0; senders[i].name; i++) { + FOR_EACH_SENDER(i) { char *tmp; ret = xasprintf(&tmp, "%s%s\n", msg? msg : "", - senders[i].name); + senders[i]->name); free(msg); msg = tmp; } @@ -344,17 +348,17 @@ static int com_sender(struct command_context *cc, struct lls_parse_result *lpr) if (scd.sender_num < 0) return ret; if (strcmp(lls_input(1, lpr), "status") == 0) - msg = senders[scd.sender_num].status(); + msg = senders[scd.sender_num]->status(); else - msg = senders[scd.sender_num].help(); + msg = senders[scd.sender_num]->help(); return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false); } switch (scd.cmd_num) { case SENDER_add: case SENDER_delete: - assert(senders[scd.sender_num].resolve_target); - ret = senders[scd.sender_num].resolve_target(lls_input(2, lpr), + assert(senders[scd.sender_num]->resolve_target); + ret = senders[scd.sender_num]->resolve_target(lls_input(2, lpr), &scd); if (ret < 0) return ret; @@ -493,9 +497,21 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr) bool parser_friendly = SERVER_CMD_OPT_GIVEN(STAT, PARSER_FRIENDLY, lpr) > 0; uint32_t num = SERVER_CMD_UINT32_VAL(STAT, NUM, lpr); + const struct timespec ts = {.tv_sec = 50, .tv_nsec = 0}; - para_sigaction(SIGUSR1, dummy); + para_sigaction(SIGINT, SIG_IGN); + para_sigaction(SIGUSR1, command_handler_sighandler); + para_sigaction(SIGTERM, command_handler_sighandler); + /* + * Simply checking subcmd_should_die is racy because a signal may + * arrive after the check but before the subsequent call to sleep(3). + * If this happens, sleep(3) would not be interrupted by the signal. + * To avoid this we block SIGTERM here and allow it to arrive only + * while we sleep. + */ + para_block_signal(SIGTERM); for (;;) { + sigset_t set; /* * Copy the mmd structure to minimize the time we hold the mmd * lock. @@ -518,7 +534,15 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr) ret = 1; if (num > 0 && !--num) goto out; - sleep(50); + sigemptyset(&set); /* empty set means: unblock all signals */ + /* + * pselect(2) allows to atomically unblock signals, then go to + * sleep. Calling sigprocmask(2) followed by sleep(3) would + * open a race window similar to the one described above. + */ + pselect(1, NULL, NULL, NULL, &ts, &set); + if (subcmd_should_die) + goto out; ret = -E_SERVER_CRASH; if (getppid() == 1) goto out; @@ -758,7 +782,7 @@ struct connection_features { int dummy; /* none at the moment */ }; -static int parse_auth_request(char *buf, int len, struct user **u, +static int parse_auth_request(char *buf, int len, const struct user **u, struct connection_features *cf) { int ret; @@ -792,7 +816,7 @@ static int parse_auth_request(char *buf, int len, struct user **u, } } PARA_DEBUG_LOG("received auth request for user %s\n", username); - *u = lookup_user(username); + *u = user_list_lookup(username); ret = 1; out: free_argv(features); @@ -874,9 +898,11 @@ static int run_command(struct command_context *cc, struct iovec *iov) * the function if the connection was not authenticated when the timeout * expires. * + * \return Standard. + * * \sa alarm(2), \ref openssl.c, \ref crypt.h. */ -__noreturn void handle_connect(int fd) +int handle_connect(int fd) { int ret; unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN]; @@ -986,5 +1012,5 @@ out: } sc_free(cc->scc.recv); sc_free(cc->scc.send); - exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); + return ret; } diff --git a/command.h b/command.h index 0265f056..a7fa4673 100644 --- a/command.h +++ b/command.h @@ -3,7 +3,7 @@ /** Per connection data available to command handlers. */ struct command_context { /** The paraslash user that executes this command. */ - struct user *u; + const struct user *u; /** File descriptor and crypto keys. */ struct stream_cipher_context scc; }; diff --git a/daemon.c b/daemon.c index ddfe680c..bfa81487 100644 --- a/daemon.c +++ b/daemon.c @@ -31,6 +31,12 @@ struct daemon { /** Used for colored log messages. */ char log_colors[NUM_LOGLEVELS][COLOR_MAXLEN]; char *old_cwd; + /* + * If these pointers are non-NULL, the functions are called from + * daemon_log() before and after writing each log message. + */ + void (*pre_log_hook)(void); + void (*post_log_hook)(void); }; static struct daemon the_daemon, *me = &the_daemon; @@ -140,6 +146,12 @@ void daemon_set_loglevel(const char *loglevel) me->loglevel = ret; } +void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void)) +{ + me->pre_log_hook = pre_log_hook; + me->post_log_hook = post_log_hook; +} + /** * Set one of the daemon config flags. * @@ -409,6 +421,8 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...) return; fp = me->logfile? me->logfile : stderr; + if (me->pre_log_hook) + me->pre_log_hook(); color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL; if (color) fprintf(fp, "%s", color); @@ -441,4 +455,6 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...) va_end(argp); if (color) fprintf(fp, "%s", COLOR_RESET); + if (me->post_log_hook) + me->post_log_hook(); } diff --git a/daemon.h b/daemon.h index 989678df..b530b0d7 100644 --- a/daemon.h +++ b/daemon.h @@ -11,6 +11,7 @@ void daemon_set_start_time(void); time_t daemon_get_uptime(const struct timeval *current_time); __malloc char *daemon_get_uptime_str(const struct timeval *current_time); void daemon_set_logfile(const char *logfile_name); +void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void)); void daemon_set_flag(unsigned flag); void daemon_set_loglevel(const char *loglevel); bool daemon_init_colors_or_die(int color_arg, int color_arg_auto, diff --git a/dccp_send.c b/dccp_send.c index 69ba65f5..770ac601 100644 --- a/dccp_send.c +++ b/dccp_send.c @@ -28,9 +28,6 @@ #include "sched.h" #include "vss.h" #include "fd.h" -#include "close_on_fork.h" -#include "chunk_queue.h" -#include "acl.h" static struct sender_status dccp_sender_status, *dss = &dccp_sender_status; @@ -83,6 +80,12 @@ static void dccp_shutdown_clients(void) dccp_shutdown_client(sc); } +static void dccp_shutdown(void) +{ + dccp_shutdown_clients(); + generic_acl_deplete(&dss->acl); +} + /** * Obtain current MPS according to RFC 4340, sec. 14. */ static int dccp_init_fec(struct sender_client *sc) { @@ -215,32 +218,39 @@ static char *dccp_status(void) return result; } -/** - * The init function of the dccp sender. - * - * \param s pointer to the dccp sender struct. - * - * It initializes all function pointers of \a s and starts - * listening on the given port. +/* + * Initialize the client list and the access control list and listen on the + * dccp port. */ -void dccp_send_init(struct sender *s) +static void dccp_send_init(void) { - s->status = dccp_status; - s->send = NULL; - s->pre_select = dccp_pre_select; - s->post_select = dccp_post_select; - s->shutdown_clients = dccp_shutdown_clients; - s->resolve_target = NULL; - s->help = generic_sender_help; - s->client_cmds[SENDER_on] = dccp_com_on; - s->client_cmds[SENDER_off] = dccp_com_off; - s->client_cmds[SENDER_deny] = dccp_com_deny; - s->client_cmds[SENDER_allow] = dccp_com_allow; - s->client_cmds[SENDER_add] = NULL; - s->client_cmds[SENDER_delete] = NULL; - init_sender_status(dss, OPT_RESULT(DCCP_ACCESS), OPT_UINT32_VAL(DCCP_PORT), OPT_UINT32_VAL(DCCP_MAX_CLIENTS), OPT_GIVEN(DCCP_DEFAULT_DENY)); generic_com_on(dss, IPPROTO_DCCP); } + +/** + * The DCCP sender. + * + * This sender offers congestion control not available in plain TCP. Most + * methods of the sender structure are implemented as simple wrappers for the + * generic functions defined in \ref send_common.c. Like UDP streams, DCCP + * streams are sent FEC-encoded. + */ +const struct sender dccp_sender = { + .name = "dccp", + .init = dccp_send_init, + .shutdown = dccp_shutdown, + .pre_select = dccp_pre_select, + .post_select = dccp_post_select, + .shutdown_clients = dccp_shutdown_clients, + .client_cmds = { + [SENDER_on] = dccp_com_on, + [SENDER_off] = dccp_com_off, + [SENDER_deny] = dccp_com_deny, + [SENDER_allow] = dccp_com_allow, + }, + .help = generic_sender_help, + .status = dccp_status, +}; diff --git a/error.h b/error.h index 1904a6d6..02f42246 100644 --- a/error.h +++ b/error.h @@ -70,6 +70,7 @@ PARA_ERROR(BTR_EOF, "buffer tree: end of file"), \ PARA_ERROR(BTR_NAVAIL, "btr node: value currently unavailable"), \ PARA_ERROR(BTR_NO_CHILD, "btr node has no children"), \ + PARA_ERROR(CHILD_CONTEXT, "now running in child context"), \ PARA_ERROR(CHMOD, "failed to set socket mode"), \ PARA_ERROR(CLIENT_SYNTAX, "syntax error"), \ PARA_ERROR(CLIENT_WRITE, "client write error"), \ @@ -224,6 +225,7 @@ PARA_ERROR(TARGET_EXISTS, "requested target is already present"),\ PARA_ERROR(TARGET_NOT_FOUND, "requested target not found"), \ PARA_ERROR(TASK_STARTED, "task started"), \ + PARA_ERROR(DEADLY_SIGNAL, "termination request by signal"), \ PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \ PARA_ERROR(UCRED_PERM, "permission denied"), \ PARA_ERROR(UDP_OVERRUN, "output buffer overrun"), \ diff --git a/fd.c b/fd.c index 0347fd83..33891d2e 100644 --- a/fd.c +++ b/fd.c @@ -419,34 +419,6 @@ void para_fd_set(int fd, fd_set *fds, int *max_fileno) *max_fileno = PARA_MAX(*max_fileno, fd); } -/** - * Paraslash's wrapper for fgets(3). - * - * \param line Pointer to the buffer to store the line. - * \param size The size of the buffer given by \a line. - * \param f The stream to read from. - * - * \return Unlike the standard fgets() function, an integer value - * is returned. On success, this function returns 1. On errors, -E_FGETS - * is returned. A zero return value indicates an end of file condition. - */ -__must_check int para_fgets(char *line, int size, FILE *f) -{ -again: - if (fgets(line, size, f)) - return 1; - if (feof(f)) - return 0; - if (!ferror(f)) - return -E_FGETS; - if (errno != EINTR) { - PARA_ERROR_LOG("%s\n", strerror(errno)); - return -E_FGETS; - } - clearerr(f); - goto again; -} - /** * Paraslash's wrapper for mmap. * diff --git a/fd.h b/fd.h index 25eea8a2..c9e79426 100644 --- a/fd.h +++ b/fd.h @@ -11,7 +11,6 @@ int para_select(int n, fd_set *readfds, fd_set *writefds, __must_check int mark_fd_nonblocking(int fd); __must_check int mark_fd_blocking(int fd); void para_fd_set(int fd, fd_set *fds, int *max_fileno); -__must_check int para_fgets(char *line, int size, FILE *f); int para_mmap(size_t length, int prot, int flags, int fd, void *map); int para_open(const char *path, int flags, mode_t mode); int para_mkdir(const char *path, mode_t mode); diff --git a/http_send.c b/http_send.c index 210f85ac..4d612285 100644 --- a/http_send.c +++ b/http_send.c @@ -26,7 +26,6 @@ #include "close_on_fork.h" #include "fd.h" #include "chunk_queue.h" -#include "acl.h" /** Message sent to clients that do not send a valid get request. */ #define HTTP_ERR_MSG "HTTP/1.0 400 Bad Request\n" @@ -77,6 +76,12 @@ static void http_shutdown_clients(void) shutdown_clients(hss); } +static void http_shutdown(void) +{ + http_shutdown_clients(); + generic_acl_deplete(&hss->acl); +} + static int queue_chunk_or_shutdown(struct sender_client *sc, struct sender_status *ss, const char *buf, size_t num_bytes) { @@ -236,30 +241,12 @@ static char *http_status(void) return generic_sender_status(hss, "http"); } -/** - * The init function of the http sender. - * - * \param s Pointer to the http sender struct. - * - * It initializes all function pointers of \a s, the client list and the access - * control list. If the autostart option was given, the tcp port is opened. +/* + * Initialize the client list and the access control list, and optionally + * listen on the tcp port. */ -void http_send_init(struct sender *s) +static void http_send_init(void) { - s->status = http_status; - s->send = http_send; - s->pre_select = http_pre_select; - s->post_select = http_post_select; - s->shutdown_clients = http_shutdown_clients; - s->resolve_target = NULL; - s->help = generic_sender_help; - s->client_cmds[SENDER_on] = http_com_on; - s->client_cmds[SENDER_off] = http_com_off; - s->client_cmds[SENDER_deny] = http_com_deny; - s->client_cmds[SENDER_allow] = http_com_allow; - s->client_cmds[SENDER_add] = NULL; - s->client_cmds[SENDER_delete] = NULL; - init_sender_status(hss, OPT_RESULT(HTTP_ACCESS), OPT_UINT32_VAL(HTTP_PORT), OPT_UINT32_VAL(HTTP_MAX_CLIENTS), OPT_GIVEN(HTTP_DEFAULT_DENY)); @@ -267,3 +254,31 @@ void http_send_init(struct sender *s) return; generic_com_on(hss, IPPROTO_TCP); } + +/** + * The HTTP sender. + * + * This is the only sender which does not FEC-encode the stream. This is not + * necessary because HTTP sits on top of TCP, a reliable transport which + * retransmits lost packets automatically. The sender employs per-client queues + * which queue chunks of audio data if they can not be sent immediately because + * the write operation would block. Most methods of the sender are implemented + * as wrappers for the generic functions defined in \ref send_common.c. + */ +const struct sender http_sender = { + .name = "http", + .init = http_send_init, + .shutdown = http_shutdown, + .pre_select = http_pre_select, + .post_select = http_post_select, + .send = http_send, + .shutdown_clients = http_shutdown_clients, + .client_cmds = { + [SENDER_on] = http_com_on, + [SENDER_off] = http_com_off, + [SENDER_deny] = http_com_deny, + [SENDER_allow] = http_com_allow, + }, + .help = generic_sender_help, + .status = http_status, +}; diff --git a/send.h b/send.h index 84f35f92..7a4c01bc 100644 --- a/send.h +++ b/send.h @@ -27,14 +27,11 @@ struct sender { /** The name of the sender. */ const char *name; /** - * The init function of this sender. - * - * It must fill in all function pointers of \a s as well as the \a - * client_cmds array, see below. It should also do all necessary - * preparations to init this sending facility, for example it could - * open a tcp port. + * Parse the command line options and initialize this sender (e.g., + * initialize target or access control lists, listen on a network + * socket, etc.). */ - void (*init)(struct sender *s); + void (*init)(void); /** * Return the help text of this sender. * @@ -88,6 +85,8 @@ struct sender { * the clients aware of the end-of-file condition. */ void (*shutdown_clients)(void); + /** Dellocate all resources. Only called on exit. */ + void (*shutdown)(void); /** * Array of function pointers for the sender subcommands. * @@ -107,6 +106,11 @@ struct sender { int (*resolve_target)(const char *, struct sender_command_data *); }; +/** NULL-terminated list, defined in \ref vss.c. */ +extern const struct sender * const senders[]; +/** Iterate over all senders. */ +#define FOR_EACH_SENDER(_i) for ((_i) = 0; senders[(_i)]; (_i)++) + /** Describes one client, connected to a paraslash sender. */ struct sender_client { /** The file descriptor of the client. */ @@ -187,6 +191,7 @@ void generic_com_allow(struct sender_command_data *scd, void generic_com_deny(struct sender_command_data *scd, struct sender_status *ss); void generic_com_on(struct sender_status *ss, unsigned protocol); +void generic_acl_deplete(struct list_head *acl); void generic_com_off(struct sender_status *ss); char *generic_sender_help(void); struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfds); diff --git a/send_common.c b/send_common.c index 16820a53..61a12c82 100644 --- a/send_common.c +++ b/send_common.c @@ -46,8 +46,10 @@ void shutdown_client(struct sender_client *sc, struct sender_status *ss) { PARA_INFO_LOG("shutting down %s on fd %d\n", sc->name, sc->fd); free(sc->name); - close(sc->fd); - del_close_on_fork_list(sc->fd); + if (!process_is_command_handler()) { + close(sc->fd); + del_close_on_fork_list(sc->fd); + } cq_destroy(sc->cq); list_del(&sc->node); free(sc->private_data); @@ -188,6 +190,22 @@ void generic_com_allow(struct sender_command_data *scd, acl_allow(scd->host, scd->netmask, &ss->acl, ss->default_deny); } +/** + * Empty the access control list of a sender. + * + * \param acl The access control list of the sender. + * + * This is called from the ->shutdown methods of the http and the dccp sender. + */ +void generic_acl_deplete(struct list_head *acl) +{ + /* + * Since default_deny is false, the ACL is considered a blacklist. A + * netmask of zero matches any IP address, so this call empties the ACL. + */ + acl_allow("0.0.0.0", 0 /* netmask */, acl, 0 /* default_deny */); +} + /** * Deny connections from the given range of IP addresses. * diff --git a/server.c b/server.c index 011367e3..f19fc996 100644 --- a/server.c +++ b/server.c @@ -100,16 +100,39 @@ uint32_t afs_socket_cookie; /** The mutex protecting the shared memory area containing the mmd struct. */ int mmd_mutex; +/* Serializes log output. */ +static int log_mutex; + static struct sched sched; static struct signal_task *signal_task; /** The process id of the audio file selector process. */ pid_t afs_pid = 0; +/* The the main server process (parent of afs and the command handlers). */ +static pid_t server_pid; + +/** + * Tell whether the executing process is a command handler. + * + * Cleanup on exit must be performed differently for command handlers. + * + * \return True if the pid of the executing process is neither the server pid + * nor the afs pid. + */ +bool process_is_command_handler(void) +{ + pid_t pid = getpid(); + + return pid != afs_pid && pid != server_pid; +} + /** The task responsible for server command handling. */ struct server_command_task { /** TCP port on which para_server listens for connections. */ int listen_fd; + /* File descriptor for the accepted socket. */ + int child_fd; /** Copied from para_server's main function. */ int argc; /** Argument vector passed to para_server's main function. */ @@ -131,9 +154,17 @@ char *server_get_tasks(void) return get_task_list(&sched); } -/* - * setup shared memory area and get mutex for locking - */ +static void pre_log_hook(void) +{ + mutex_lock(log_mutex); +} + +static void post_log_hook(void) +{ + mutex_unlock(log_mutex); +} + +/* Setup shared memory area and init mutexes */ static void init_ipc_or_die(void) { void *shm; @@ -152,6 +183,10 @@ static void init_ipc_or_die(void) if (ret < 0) goto err_out; mmd_mutex = ret; + ret = mutex_new(); + if (ret < 0) + goto destroy_mmd_mutex; + log_mutex = ret; mmd->num_played = 0; mmd->num_commands = 0; @@ -161,6 +196,8 @@ static void init_ipc_or_die(void) mmd->vss_status_flags = VSS_NEXT; mmd->new_vss_status_flags = VSS_NEXT; return; +destroy_mmd_mutex: + mutex_destroy(mmd_mutex); err_out: PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); @@ -246,7 +283,7 @@ success: daemon_set_flag(DF_LOG_TIMING); daemon_set_priority(OPT_UINT32_VAL(PRIORITY)); if (user_list_file) - init_user_list(user_list_file); + user_list_init(user_list_file); ret = 1; free_cf: free(cf); @@ -274,8 +311,12 @@ static void handle_sighup(void) static int signal_post_select(struct sched *s, __a_unused void *context) { - int signum = para_next_signal(&s->rfds); + int ret, signum; + ret = task_get_notification(signal_task->task); + if (ret < 0) + return ret; + signum = para_next_signal(&s->rfds); switch (signum) { case 0: return 0; @@ -285,7 +326,7 @@ static int signal_post_select(struct sched *s, __a_unused void *context) case SIGCHLD: for (;;) { pid_t pid; - int ret = para_reap_child(&pid); + ret = para_reap_child(&pid); if (ret <= 0) break; if (pid != afs_pid) @@ -301,27 +342,22 @@ static int signal_post_select(struct sched *s, __a_unused void *context) PARA_EMERG_LOG("terminating on signal %d\n", signum); kill(0, SIGTERM); /* - * We must wait for afs because afs catches SIGINT/SIGTERM. - * Before reacting to the signal, afs might want to use the + * We must wait for all of our children to die. For the afs + * process or a command handler might want to use the * shared memory area and the mmd mutex. If we destroy this * mutex too early and afs tries to lock the shared memory * area, the call to mutex_lock() will fail and terminate the * afs process. This leads to dirty osl tables. - * - * There's no such problem with the other children of the - * server process (the command handlers) as these reset their - * SIGINT/SIGTERM handlers to the default action, i.e. these - * processes get killed immediately by the above kill(). */ - PARA_INFO_LOG("waiting for afs (pid %d) to die\n", - (int)afs_pid); - waitpid(afs_pid, NULL, 0); + PARA_INFO_LOG("waiting for child processes to die\n"); + mutex_unlock(mmd_mutex); + while (wait(NULL) != -1 || errno != ECHILD) + ; /* still at least one child alive */ + mutex_lock(mmd_mutex); cleanup: free(mmd->afd.afhi.chunk_table); - close_listed_fds(); - mutex_destroy(mmd_mutex); - shm_detach(mmd); - exit(EXIT_FAILURE); + task_notify_all(s, E_DEADLY_SIGNAL); + return -E_DEADLY_SIGNAL; } return 0; } @@ -353,12 +389,14 @@ static void command_pre_select(struct sched *s, void *context) static int command_post_select(struct sched *s, void *context) { struct server_command_task *sct = context; - int new_fd, ret, i; char *peer_name; pid_t child_pid; uint32_t *chunk_table; + ret = task_get_notification(sct->task); + if (ret < 0) + return ret; ret = para_accept(sct->listen_fd, &s->rfds, NULL, 0, &new_fd); if (ret <= 0) goto out; @@ -391,9 +429,7 @@ static int command_post_select(struct sched *s, void *context) PARA_INFO_LOG("accepted connection from %s\n", peer_name); /* mmd might already have changed at this point */ free(chunk_table); - alarm(ALARM_TIMEOUT); - close_listed_fds(); - signal_shutdown(signal_task); + sct->child_fd = new_fd; /* * put info on who we are serving into argv[0] to make * client ip visible in top/ps @@ -402,21 +438,34 @@ static int command_post_select(struct sched *s, void *context) memset(sct->argv[i], 0, strlen(sct->argv[i])); i = sct->argc - 1 - lls_num_inputs(cmdline_lpr); sprintf(sct->argv[i], "para_server (serving %s)", peer_name); - handle_connect(new_fd); - /* never reached*/ + /* ask other tasks to terminate */ + task_notify_all(s, E_CHILD_CONTEXT); + /* + * After we return, the scheduler calls server_select() with a minimal + * timeout value, because the remaining tasks have a notification + * pending. Next it calls the ->post_select method of these tasks, + * which will return negative in view of the notification. This causes + * schedule() to return as there are no more runnable tasks. + * + * Note that semaphores are not inherited across a fork(), so we don't + * hold the lock at this point. Since server_select() drops the lock + * prior to calling para_select(), we need to acquire it here. + */ + mutex_lock(mmd_mutex); + return -E_CHILD_CONTEXT; out: if (ret < 0) PARA_CRIT_LOG("%s\n", para_strerror(-ret)); return 0; } -static void init_server_command_task(int argc, char **argv) +static void init_server_command_task(struct server_command_task *sct, + int argc, char **argv) { int ret; - static struct server_command_task server_command_task_struct, - *sct = &server_command_task_struct; PARA_NOTICE_LOG("initializing tcp command socket\n"); + sct->child_fd = -1; sct->argc = argc; sct->argv = argv; ret = para_listen_simple(IPPROTO_TCP, OPT_UINT32_VAL(PORT)); @@ -457,6 +506,7 @@ static int init_afs(int argc, char **argv) afs_pid = getpid(); crypt_shutdown(); + user_list_deplete(); for (i = argc - 1; i >= 0; i--) memset(argv[i], 0, strlen(argv[i])); i = argc - lls_num_inputs(cmdline_lpr) - 1; @@ -494,7 +544,7 @@ static void handle_help_flags(void) exit(EXIT_SUCCESS); } -static void server_init(int argc, char **argv) +static void server_init(int argc, char **argv, struct server_command_task *sct) { int ret, afs_socket, daemon_pipe = -1; char *errctx; @@ -514,10 +564,12 @@ static void server_init(int argc, char **argv) /* become daemon */ if (OPT_GIVEN(DAEMON)) daemon_pipe = daemonize(true /* parent waits for SIGTERM */); + server_pid = getpid(); crypt_init(); daemon_log_welcome("server"); - init_ipc_or_die(); /* init mmd struct and mmd->lock */ + init_ipc_or_die(); /* init mmd struct, mmd and log mutex */ daemon_set_start_time(); + daemon_set_hooks(pre_log_hook, post_log_hook); PARA_NOTICE_LOG("initializing audio format handlers\n"); afh_init(); @@ -540,7 +592,7 @@ static void server_init(int argc, char **argv) para_unblock_signal(SIGCHLD); PARA_NOTICE_LOG("initializing virtual streaming system\n"); vss_init(afs_socket, &sched); - init_server_command_task(argc, argv); + init_server_command_task(sct, argc, argv); if (daemon_pipe >= 0) { if (write(daemon_pipe, "\0", 1) < 0) { PARA_EMERG_LOG("daemon_pipe: %s", strerror(errno)); @@ -592,6 +644,21 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds, return ret; } +/** + * Deallocate all lopsub parse results. + * + * The server allocates a parse result for command line options and optionally + * a second parse result for the effective configuration, defined by merging + * the command line options with the options stored in the configuration file. + * This function frees both structures. + */ +void free_lpr(void) +{ + lls_free_parse_result(server_lpr, CMD_PTR); + if (server_lpr != cmdline_lpr) + lls_free_parse_result(cmdline_lpr, CMD_PTR); +} + /** * The main function of para_server. * @@ -603,19 +670,38 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds, int main(int argc, char *argv[]) { int ret; + struct server_command_task server_command_task_struct, + *sct = &server_command_task_struct; sched.default_timeout.tv_sec = 1; sched.select_function = server_select; - server_init(argc, argv); + server_init(argc, argv, sct); mutex_lock(mmd_mutex); ret = schedule(&sched); + /* + * We hold the mmd lock: it was re-acquired in server_select() + * after the select call. + */ + mutex_unlock(mmd_mutex); sched_shutdown(&sched); crypt_shutdown(); - lls_free_parse_result(server_lpr, CMD_PTR); - if (server_lpr != cmdline_lpr) - lls_free_parse_result(cmdline_lpr, CMD_PTR); - if (ret < 0) - PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + signal_shutdown(signal_task); + if (!process_is_command_handler()) { /* parent (server) */ + mutex_destroy(mmd_mutex); + daemon_set_hooks(NULL, NULL); /* only one process remaining */ + mutex_destroy(log_mutex); + deplete_close_on_fork_list(); + if (ret < 0) + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + } else { + alarm(ALARM_TIMEOUT); + close_listed_fds(); + ret = handle_connect(sct->child_fd); + } + vss_shutdown(); + shm_detach(mmd); + user_list_deplete(); + free_lpr(); exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); } diff --git a/server.h b/server.h index 988a98d8..da75d86b 100644 --- a/server.h +++ b/server.h @@ -109,6 +109,8 @@ extern struct lls_parse_result *server_lpr; #define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \ lls_opt(LSG_SERVER_PARA_SERVER_OPT_ ## _name, CMD_PTR))) -__noreturn void handle_connect(int fd); +int handle_connect(int fd); void parse_config_or_die(bool reload); char *server_get_tasks(void); +bool process_is_command_handler(void); +void free_lpr(void); diff --git a/udp_send.c b/udp_send.c index 38d49e3e..04e2982f 100644 --- a/udp_send.c +++ b/udp_send.c @@ -72,8 +72,11 @@ static void udp_delete_target(struct sender_client *sc, const char *msg) PARA_NOTICE_LOG("deleting %s (%s) from list\n", sc->name, msg); udp_close_target(sc); - close(sc->fd); - del_close_on_fork_list(sc->fd); + /* command handlers already called close_listed_fds() */ + if (!process_is_command_handler()) { + close(sc->fd); + del_close_on_fork_list(sc->fd); + } vss_del_fec_client(ut->fc); list_del(&sc->node); free(sc->name); @@ -164,6 +167,13 @@ static void udp_shutdown_targets(void) udp_close_target(sc); } +static void udp_shutdown(void) +{ + struct sender_client *sc, *tmp; + list_for_each_entry_safe(sc, tmp, &targets, node) + udp_delete_target(sc, "shutdown"); +} + static int udp_resolve_target(const char *url, struct sender_command_data *scd) { const char *result; @@ -412,32 +422,38 @@ static char *udp_help(void) ); } -/** - * The init function of para_server's udp sender. - * - * \param s Pointer to the udp sender struct. - * - * It initializes all function pointers of \a s and the list of udp targets. - */ -void udp_send_init(struct sender *s) +/* Initialize the list of udp targets. */ +static void udp_send_init(void) { INIT_LIST_HEAD(&targets); - s->status = udp_status; - s->help = udp_help; - s->send = NULL; - s->pre_select = NULL; - s->post_select = NULL; - s->shutdown_clients = udp_shutdown_targets; - s->resolve_target = udp_resolve_target; - s->client_cmds[SENDER_on] = udp_com_on; - s->client_cmds[SENDER_off] = udp_com_off; - s->client_cmds[SENDER_deny] = NULL; - s->client_cmds[SENDER_allow] = NULL; - s->client_cmds[SENDER_add] = udp_com_add; - s->client_cmds[SENDER_delete] = udp_com_delete; sender_status = SENDER_off; udp_init_target_list(); if (!OPT_GIVEN(UDP_NO_AUTOSTART)) sender_status = SENDER_on; PARA_DEBUG_LOG("udp sender init complete\n"); } + +/** + * The UDP sender. + * + * In contrast to the other senders the UDP sender is active in the sense that + * it initiates the network connection according to its list of targets rather + * than passively waiting for clients to connect. Like DCCP streams, UDP + * streams are always sent FEC-encoded. The UDP sender is the only sender which + * supports IP multicasting. + */ +const struct sender udp_sender = { + .name = "udp", + .init = udp_send_init, + .shutdown = udp_shutdown, + .shutdown_clients = udp_shutdown_targets, + .resolve_target = udp_resolve_target, + .client_cmds = { + [SENDER_on] = udp_com_on, + [SENDER_off] = udp_com_off, + [SENDER_add] = udp_com_add, + [SENDER_delete] = udp_com_delete, + }, + .help = udp_help, + .status = udp_status, +}; diff --git a/user_list.c b/user_list.c index 9db08715..32a4309d 100644 --- a/user_list.c +++ b/user_list.c @@ -13,30 +13,78 @@ #include "list.h" #include "user_list.h" -static struct list_head user_list; +static INITIALIZED_LIST_HEAD(user_list); /* - * Fill the list of users known to para_server. + * Wrapper for fgets(3). * - * Populates a linked list of all users in \a user_list_file. Returns on - * success, calls exit() on errors. + * Unlike fgets(3), an integer value is returned. On success, this function + * returns 1. On errors, -E_FGETS is returned. A zero return value indicates an + * end of file condition. */ -static void populate_user_list(char *user_list_file) +static int xfgets(char *line, int size, FILE *f) +{ +again: + if (fgets(line, size, f)) + return 1; + if (feof(f)) + return 0; + if (!ferror(f)) + return -E_FGETS; + if (errno != EINTR) { + PARA_ERROR_LOG("%s\n", strerror(errno)); + return -E_FGETS; + } + clearerr(f); + goto again; +} + +/** + * Remove all entries from the user list. + * + * This is called on shutdown and when the user list is reloaded because the + * server received SIGHUP. + */ +void user_list_deplete(void) +{ + struct user *u, *tmpu; + + list_for_each_entry_safe(u, tmpu, &user_list, node) { + list_del(&u->node); + free(u->name); + apc_free_pubkey(u->pubkey); + free(u); + } +} + +/** + * Initialize the list of users allowed to connect to para_server. + * + * \param user_list_file The file containing access information. + * + * If this function is called for the second time, the contents of the + * previous call are discarded, i.e. the user list is reloaded. + * + * This function either succeeds or calls exit(3). + */ +void user_list_init(const char *user_list_file) { int ret = -E_USERLIST; FILE *file_ptr = fopen(user_list_file, "r"); + struct user *u; if (!file_ptr) goto err; + + user_list_deplete(); for (;;) { int num; char line[255]; /* keyword, name, key, perms */ char w[255], n[255], k[255], p[255], tmp[4][255]; - struct user *u; struct asymmetric_key *pubkey; - ret = para_fgets(line, sizeof(line), file_ptr); + ret = xfgets(line, sizeof(line), file_ptr); if (ret <= 0) break; if (sscanf(line,"%200s %200s %200s %200s", w, n, k, p) < 3) @@ -93,32 +141,6 @@ err: exit(EXIT_FAILURE); } -/** - * Initialize the list of users allowed to connect to para_server. - * - * \param user_list_file The file containing access information. - * - * If this function is called for the second time, the contents of the - * previous call are discarded, i.e. the user list is reloaded. - */ -void init_user_list(char *user_list_file) -{ - struct user *u, *tmp; - static int initialized; - - if (initialized) { - list_for_each_entry_safe(u, tmp, &user_list, node) { - list_del(&u->node); - free(u->name); - apc_free_pubkey(u->pubkey); - free(u); - } - } else - INIT_LIST_HEAD(&user_list); - initialized = 1; - populate_user_list(user_list_file); -} - /** * Lookup a user in the user list. * @@ -127,9 +149,9 @@ void init_user_list(char *user_list_file) * \return A pointer to the corresponding user struct if the user was found, \p * NULL otherwise. */ -struct user *lookup_user(const char *name) +const struct user *user_list_lookup(const char *name) { - struct user *u; + const struct user *u; list_for_each_entry(u, &user_list, node) { if (strcmp(u->name, name)) continue; diff --git a/user_list.h b/user_list.h index 41fad164..1cb94764 100644 --- a/user_list.h +++ b/user_list.h @@ -32,5 +32,6 @@ struct user { unsigned int perms; }; -void init_user_list(char *user_list_file); -struct user *lookup_user(const char *name); +void user_list_init(const char *user_list_file); +void user_list_deplete(void); +const struct user *user_list_lookup(const char *name); diff --git a/vss.c b/vss.c index 8b850fe1..2cd0a163 100644 --- a/vss.c +++ b/vss.c @@ -40,24 +40,9 @@ extern void dccp_send_init(struct sender *); extern void http_send_init(struct sender *); extern void udp_send_init(struct sender *); -/** The list of supported senders. */ -struct sender senders[] = { - { - .name = "http", - .init = http_send_init, - }, - { - .name = "dccp", - .init = dccp_send_init, - }, - { - .name = "udp", - .init = udp_send_init, - }, - { - .name = NULL, - } -}; +extern const struct sender udp_sender, dccp_sender, http_sender; +const struct sender * const senders[] = { + &http_sender, &dccp_sender, &udp_sender, NULL}; /** The possible states of the afs socket. */ enum afs_socket_status { @@ -918,10 +903,10 @@ static void vss_pre_select(struct sched *s, void *context) vsst->afsss = AFS_SOCKET_CHECK_FOR_WRITE; } else para_fd_set(vsst->afs_socket, &s->rfds, &s->max_fileno); - for (i = 0; senders[i].name; i++) { - if (!senders[i].pre_select) + FOR_EACH_SENDER(i) { + if (!senders[i]->pre_select) continue; - senders[i].pre_select(&s->max_fileno, &s->rfds, &s->wfds); + senders[i]->pre_select(&s->max_fileno, &s->rfds, &s->wfds); } vss_compute_timeout(s, vsst); } @@ -1082,10 +1067,10 @@ static void vss_send(struct vss_task *vsst) * We call ->send() even if len is zero because senders might * have data queued which can be sent now. */ - for (i = 0; senders[i].name; i++) { - if (!senders[i].send) + FOR_EACH_SENDER(i) { + if (!senders[i]->send) continue; - senders[i].send(mmd->current_chunk, mmd->chunks_sent, + senders[i]->send(mmd->current_chunk, mmd->chunks_sent, buf, len, vsst->header_buf, vsst->header_len); } } @@ -1098,12 +1083,17 @@ static int vss_post_select(struct sched *s, void *context) int ret, i; struct vss_task *vsst = context; + ret = task_get_notification(vsst->task); + if (ret < 0) { + afh_free_header(vsst->header_buf, mmd->afd.audio_format_id); + return ret; + } if (!vsst->map || vss_next() || vss_paused() || vss_repos()) { /* shut down senders and fec clients */ struct fec_client *fc, *tmp; - for (i = 0; senders[i].name; i++) - if (senders[i].shutdown_clients) - senders[i].shutdown_clients(); + FOR_EACH_SENDER(i) + if (senders[i]->shutdown_clients) + senders[i]->shutdown_clients(); list_for_each_entry_safe(fc, tmp, &fec_client_list, node) fc->state = FEC_STATE_NONE; mmd->stream_start.tv_sec = 0; @@ -1129,8 +1119,8 @@ static int vss_post_select(struct sched *s, void *context) int num = mmd->sender_cmd_data.cmd_num, sender_num = mmd->sender_cmd_data.sender_num; - if (senders[sender_num].client_cmds[num]) { - ret = senders[sender_num].client_cmds[num] + if (senders[sender_num]->client_cmds[num]) { + ret = senders[sender_num]->client_cmds[num] (&mmd->sender_cmd_data); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); @@ -1147,10 +1137,10 @@ static int vss_post_select(struct sched *s, void *context) else vsst->afsss = AFS_SOCKET_AFD_PENDING; } - for (i = 0; senders[i].name; i++) { - if (!senders[i].post_select) + FOR_EACH_SENDER(i) { + if (!senders[i]->post_select) continue; - senders[i].post_select(&s->rfds, &s->wfds); + senders[i]->post_select(&s->rfds, &s->wfds); } if ((vss_playing() && !(mmd->vss_status_flags & VSS_PLAYING)) || (vss_next() && vss_playing())) @@ -1179,9 +1169,9 @@ void vss_init(int afs_socket, struct sched *s) ms2tv(announce_time, &vsst->announce_tv); PARA_INFO_LOG("announce timeval: %lums\n", tv2ms(&vsst->announce_tv)); INIT_LIST_HEAD(&fec_client_list); - for (i = 0; senders[i].name; i++) { - PARA_NOTICE_LOG("initializing %s sender\n", senders[i].name); - senders[i].init(&senders[i]); + FOR_EACH_SENDER(i) { + PARA_NOTICE_LOG("initializing %s sender\n", senders[i]->name); + senders[i]->init(); } mmd->sender_cmd_data.cmd_num = -1; if (OPT_GIVEN(AUTOPLAY)) { @@ -1200,3 +1190,20 @@ void vss_init(int afs_socket, struct sched *s) .context = vsst, }, s); } + +/** + * Turn off the virtual streaming system. + * + * This is only executed on exit. It calls the ->shutdowwn method of all senders. + */ +void vss_shutdown(void) +{ + int i; + + FOR_EACH_SENDER(i) { + if (!senders[i]->shutdown) + continue; + PARA_NOTICE_LOG("shutting down %s sender\n", senders[i]->name); + senders[i]->shutdown(); + } +} diff --git a/vss.h b/vss.h index 5ebcc4e4..46bb0e73 100644 --- a/vss.h +++ b/vss.h @@ -9,6 +9,7 @@ unsigned int vss_repos(void); unsigned int vss_paused(void); unsigned int vss_stopped(void); struct timeval *vss_chunk_time(void); +void vss_shutdown(void); /** Stop playing after current audio file. */ #define VSS_NOMORE 1