/* SPDX-License-Identifier: GPL-2.0 */ /** \file server.c Paraslash's main server. * * This file implements the main function of para_server(1) and its core * functionality, including signal handling and task setup/teardown and TCP * socket management. * * The file also contains a few helpers for other subsystems. */ #include #include #include #include #include #include #include #include #include #include "server.lsg.h" #include "para.h" #include "error.h" #include "lsu.h" #include "crypt.h" #include "afh.h" #include "string.h" #include "afs.h" #include "net.h" #include "list.h" #include "server.h" #include "sched.h" #include "send.h" #include "vss.h" #include "daemon.h" #include "ipc.h" #include "fd.h" #include "signal.h" #include "color.h" /** \cond doxygen_ignore */ /* Get a reference to the supercommand of para_server. */ #define CMD_PTR (lls_cmd(0, server_suite)) /* Array of error strings. */ DEFINE_PARA_ERRLIST; __printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log; /** \endcond */ /** * Pointer to shared memory area for communication between para_server * and its children. Exported to vss.c, command.c and to afs. */ struct misc_meta_data *mmd; /** * The active value for all config options of para_server. * * It is computed by merging the parse result of the command line options with * the parse result of the config file. */ struct lls_parse_result *server_lpr = NULL; /* Command line options (no config file options). Used in handle_sighup(). */ static struct lls_parse_result *cmdline_lpr; /** The mutex protecting the shared memory area containing the mmd struct. */ int mmd_mutex; /* Serializes log output. */ static int log_mutex; /** The process id of the audio file selector process. */ pid_t afs_pid = 0; /* The main server process (parent of afs and the command handlers). */ static pid_t server_pid; static INITIALIZED_LIST_HEAD(user_list); /** Initialized from the argument to --announce-time. */ struct timeval announce_tv; /* * Wrapper for fgets(3). * * 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 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. */ static 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. This function may be * called more than once. Subsequent calls reload the user list by removing * any existing entries of the user list. The function either succeeds or * calls exit(3). */ static 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 asymmetric_key *pubkey; ret = xfgets(line, sizeof(line), file_ptr); if (ret <= 0) break; if (sscanf(line,"%200s %200s %200s %200s", w, n, k, p) < 3) continue; if (strcmp(w, "user")) continue; PARA_INFO_LOG("loading pubkey %s for user %s\n", k, n); ret = apc_get_pubkey(k, &pubkey); if (ret < 0) { PARA_NOTICE_LOG("skipping entry for user %s: %s\n", n, para_strerror(-ret)); continue; } /* * In order to encrypt len := APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN * bytes using RSA_public_encrypt() with EME-OAEP padding mode, * RSA_size(rsa) must be greater than len + 41. So ignore keys * which are too short. For details see RSA_public_encrypt(3). */ if (ret <= APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) { PARA_WARNING_LOG("public key %s too short (%d)\n", k, ret); apc_free_pubkey(pubkey); continue; } u = alloc(sizeof(*u)); u->name = para_strdup(n); u->pubkey = pubkey; u->perms = 0; num = sscanf(p, "%200[A-Z_],%200[A-Z_],%200[A-Z_],%200[A-Z_]", tmp[0], tmp[1], tmp[2], tmp[3]); PARA_DEBUG_LOG("found %i perm entries\n", num); while (num > 0) { num--; if (!strcmp(tmp[num], "VSS_READ")) u->perms |= VSS_READ; else if (!strcmp(tmp[num], "VSS_WRITE")) u->perms |= VSS_WRITE; else if (!strcmp(tmp[num], "AFS_READ")) u->perms |= AFS_READ; else if (!strcmp(tmp[num], "AFS_WRITE")) u->perms |= AFS_WRITE; else /* unknown permission */ PARA_WARNING_LOG("ignoring unknown permission: %s\n", tmp[num]); } para_list_add(&u->node, &user_list); } fclose(file_ptr); if (ret >= 0) return; err: PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } /** * Look up a user in the user list. * * \param name The name of the user. * * \return A pointer to the corresponding user struct if the user was found, * NULL otherwise. */ const struct user *user_list_lookup(const char *name) { const struct user *u; list_for_each_entry(u, &user_list, node) { if (strcmp(u->name, name)) continue; return u; } return NULL; } static INITIALIZED_LIST_HEAD(close_on_fork_list); /* Describes an element of the close-on-fork list. */ struct close_on_fork_entry { int fd; /* The file descriptor which should be closed after fork(). */ struct list_head node; /* The position in the close-on-fork list. */ }; /** * Add one file descriptor to the close-on-fork list. * * \param fd The file descriptor to add. */ void add_close_on_fork_list(int fd) { struct close_on_fork_entry *e = alloc(sizeof(*e)); e->fd = fd; para_list_add(&e->node, &close_on_fork_list); } /** * Delete one file descriptor from the close-on-fork list. * * \param fd The file descriptor to delete. * * Noop if fd does not belong to the close-on-fork list. */ void del_close_on_fork_list(int fd) { struct close_on_fork_entry *e, *tmp; list_for_each_entry_safe(e, tmp, &close_on_fork_list, node) { if (fd != e->fd) continue; list_del(&e->node); free(e); } } /* Destroy all entries, optionally close the fds as we go. */ static void deplete_cof_list(bool close_fds) { struct close_on_fork_entry *e, *tmp; list_for_each_entry_safe(e, tmp, &close_on_fork_list, node) { PARA_DEBUG_LOG("closing fd %d\n", e->fd); if (close_fds) close(e->fd); list_del(&e->node); free(e); } } static int parse_fec_parms(const char *arg, struct sender_command_data *scd) { int32_t val; char *a = para_strdup(arg), *b = strchr(a, ':'), *c = strrchr(a, ':'); int ret = -E_COMMAND_SYNTAX; if (!b || !c) goto out; *b = *c = '\0'; ret = para_atoi32(a, &val); if (ret < 0) goto out; /* optional max_slice_bytes (0 means "use MTU") */ if (b == c) { scd->max_slice_bytes = 0; } else { if (val < 0 || val > 65535) goto fec_einval; scd->max_slice_bytes = val; ret = para_atoi32(b + 1, &val); if (ret < 0) goto out; } /* k = data_slices_per_group */ if (val < 0 || val > 255) goto fec_einval; scd->data_slices_per_group = val; /* n = slices_per_group */ ret = para_atoi32(c + 1, &val); if (ret < 0) goto out; if (val < 0 || val < scd->data_slices_per_group) goto fec_einval; scd->slices_per_group = val; ret = 0; out: free(a); return ret; fec_einval: ret = -ERRNO_TO_PARA_ERROR(EINVAL); goto out; } /** * Parse a FEC URL string. * * \param arg the URL string to parse. * \param scd The structure containing host, port and the FEC parameters. * * \return Standard. * * A FEC URL consists of an ordinary URL string according to RFC 3986, * optionally followed by a slash and the three FEC parameters slice_size, * data_slices_per_group and slices_per_group. The three FEC parameters are * separated by colons. * * \sa \ref parse_url(). */ int parse_fec_url(const char *arg, struct sender_command_data *scd) { char *a = para_strdup(arg), *p = strchr(a, '/'); int ret = 0; /* default fec parameters */ scd->max_slice_bytes = 0; scd->data_slices_per_group = 14; scd->slices_per_group = 16; if (p) { *p = '\0'; ret = parse_fec_parms(p + 1, scd); if (ret < 0) goto out; } if (!parse_url(a, scd->host, sizeof(scd->host), &scd->port)) ret = -ERRNO_TO_PARA_ERROR(EINVAL); out: free(a); return ret; } /** * 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 { unsigned num_listen_fds; /* only one by default */ /** TCP socket(s) on which para_server listens for connections. */ int *listen_fds; int afs_fd; /* Obtained by accepting a connection on the listening socket. */ int client_fd; /** Copied from para_server's main function. */ int argc; /** Argument vector passed to para_server's main function. */ char **argv; /** The command task structure for scheduling. */ struct task *task; }; 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; int shmid, ret = shm_new(sizeof(struct misc_meta_data)); if (ret < 0) goto err_out; shmid = ret; ret = shm_attach(shmid, ATTACH_RW, &shm); shm_destroy(shmid); if (ret < 0) goto err_out; mmd = shm; ret = mutex_new(); 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; mmd->events = 0; mmd->num_connects = 0; mmd->active_connections = 0; mmd->vss_status_flags = VSS_NEXT; mmd->new_vss_status_flags = VSS_NEXT; mmd->loglevel = OPT_UINT32_VAL(LOGLEVEL); return; destroy_mmd_mutex: mutex_destroy(mmd_mutex); err_out: PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } /** * (Re-)read the server configuration files. * * \param reload Whether config file overrides command line. * * This function also re-opens the logfile and the user list. On SIGHUP it is * called from both server and afs context. */ void parse_config_or_die(bool reload) { int ret; unsigned flags = MCF_DONT_FREE; if (server_lpr != cmdline_lpr) lls_free_parse_result(server_lpr, CMD_PTR); server_lpr = cmdline_lpr; if (reload) flags |= MCF_OVERRIDE; ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE), "server.conf", &server_lpr, CMD_PTR, server_suite, flags); if (ret < 0) { PARA_EMERG_LOG("failed to parse config file: %s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL)); if (OPT_GIVEN(LOGFILE)) { daemon_set_logfile(OPT_STRING_VAL(LOGFILE)); daemon_open_log_or_die(); } if (daemon_init_colors_or_die(OPT_UINT32_VAL(COLOR), COLOR_AUTO, COLOR_NO, OPT_GIVEN(LOGFILE))) { int i; for (i = 0; i < OPT_GIVEN(LOG_COLOR); i++) daemon_set_log_color_or_die(lls_string_val(i, OPT_RESULT(LOG_COLOR))); } daemon_set_flag(DF_LOG_PID); daemon_set_flag(DF_LOG_LL); daemon_set_flag(DF_LOG_TIME); if (OPT_GIVEN(LOG_TIMING)) daemon_set_flag(DF_LOG_TIMING); daemon_set_priority(OPT_UINT32_VAL(PRIORITY)); if (!reload || getpid() != afs_pid) { char *user_list_file; if (OPT_GIVEN(USER_LIST)) user_list_file = para_strdup(OPT_STRING_VAL(USER_LIST)); else { char *home = para_homedir(); user_list_file = make_message("%s/.paraslash/server.users", home); free(home); } user_list_init(user_list_file); free(user_list_file); } } /* Called when server receives SIGHUP or user runs the hup subcommand. */ static void handle_sighup(void) { PARA_NOTICE_LOG("SIGHUP\n"); parse_config_or_die(true); ms2tv(OPT_UINT32_VAL(ANNOUNCE_TIME), &announce_tv); if (afs_pid != 0) kill(afs_pid, SIGHUP); } /* * Returns negative error code on errors, zero if no child died, one * otherwise. It is considered a fatal error if the afs process died. */ static int reap_child(void) { const char *child_name; int status, ll, ret; pid_t pid = waitpid(-1, &status, WNOHANG); if (pid < 0) return -ERRNO_TO_PARA_ERROR(errno); if (pid == 0) return 0; if (pid == afs_pid) { child_name = "afs"; ll = LL_ERROR; ret = -ERRNO_TO_PARA_ERROR(ECHILD); } else { child_name = "command handler"; ll = LL_INFO; ret = 1; } if (WIFEXITED(status)) para_log(ll, "%s exited. Exit status: %i\n", child_name, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) para_log(ll, "%s was killed by signal %i\n", child_name, WTERMSIG(status)); else para_log(ll, "%s terminated abormally\n", child_name); return ret; } static int signal_post_monitor(struct sched *s, void *context) { struct signal_task *st = context; int ret, signum; ret = task_get_notification(st->task); if (ret < 0) return ret; signum = para_next_signal(); switch (signum) { case 0: return 0; case SIGHUP: handle_sighup(); return 0; case SIGCHLD: for (;;) { ret = reap_child(); if (ret < 0) goto genocide; if (ret == 0) return 0; } /* die on sigint/sigterm. Kill all children too. */ case SIGINT: case SIGTERM: PARA_EMERG_LOG("terminating on signal %d\n", signum); genocide: kill(0, SIGTERM); /* * 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. */ 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); free(mmd->afd.afhi.chunk_table); task_notify_all(s, E_DEADLY_SIGNAL); return -E_DEADLY_SIGNAL; } assert(0); } static void register_signal_task(struct sched *sched) { static struct signal_task signal_task; signal_task.fd = signal_init(); para_install_sighandler(SIGINT); para_install_sighandler(SIGTERM); para_install_sighandler(SIGHUP); para_install_sighandler(SIGCHLD); para_sigaction(SIGPIPE, SIG_IGN); add_close_on_fork_list(signal_task.fd); signal_task.task = task_register(&(struct task_info) { .name = "signal", .pre_monitor = signal_pre_monitor, .post_monitor = signal_post_monitor, .context = &signal_task, }, sched); } static void command_pre_monitor(struct sched *s, void *context) { unsigned n; struct server_command_task *sct = context; for (n = 0; n < sct->num_listen_fds; n++) sched_monitor_readfd(sct->listen_fds[n], s); } static int command_task_accept(unsigned listen_idx, struct sched *s, struct server_command_task *sct) { int new_fd, ret, i; char *peer_name; pid_t child_pid; uint32_t *chunk_table; ret = para_accept(sct->listen_fds[listen_idx], NULL, 0, &new_fd); if (ret <= 0) goto out; mmd->num_connects++; mmd->active_connections++; /* * The chunk table is a pointer located in the mmd struct that points * to dynamically allocated memory, i.e. it must be freed by the parent * and the child. However, as the mmd struct is in a shared memory * area, there's no guarantee that after the fork this pointer is still * valid in child context. As it is not used in the child anyway, we * save it to a local variable before the fork and free the memory via * that copy in the child directly after the fork. */ chunk_table = mmd->afd.afhi.chunk_table; child_pid = fork(); if (child_pid < 0) { ret = -ERRNO_TO_PARA_ERROR(errno); goto out; } if (child_pid) { /* avoid problems with non-fork-safe PRNGs */ unsigned char buf[16]; get_random_bytes_or_die(buf, sizeof(buf)); close(new_fd); /* parent keeps accepting connections */ return 0; } peer_name = remote_name(new_fd); PARA_INFO_LOG("accepted connection from %s\n", peer_name); /* mmd might already have changed at this point */ free(chunk_table); sct->client_fd = new_fd; /* * put info on who we are serving into argv[0] to make * client ip visible in top/ps */ for (i = sct->argc - 1; i >= 0; i--) 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); /* 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_monitor 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_poll() drops the lock * prior to calling poll(), 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 int command_post_monitor(struct sched *s, void *context) { struct server_command_task *sct = context; unsigned n; int ret; ret = task_get_notification(sct->task); if (ret < 0) goto fail; for (n = 0; n < sct->num_listen_fds; n++) { ret = command_task_accept(n, s, sct); if (ret < 0) goto fail; } return 0; fail: free(sct->listen_fds); return ret; } static void register_command_task(struct server_command_task *sct, struct sched *sched, int argc, char **argv) { int ret; unsigned n; uint32_t port = OPT_UINT32_VAL(PORT); PARA_NOTICE_LOG("initializing tcp command socket\n"); sct->client_fd = -1; sct->argc = argc; sct->argv = argv; if (!OPT_GIVEN(LISTEN_ADDRESS)) { sct->num_listen_fds = 1; sct->listen_fds = alloc(sizeof(int)); ret = para_listen_simple(IPPROTO_TCP, port); if (ret < 0) goto err; sct->listen_fds[0] = ret; } else { sct->num_listen_fds = OPT_GIVEN(LISTEN_ADDRESS); sct->listen_fds = alloc(sct->num_listen_fds * sizeof(int)); for (n = 0; n < OPT_GIVEN(LISTEN_ADDRESS); n++) { const char *arg; arg = lls_string_val(n, OPT_RESULT(LISTEN_ADDRESS)); ret = para_listen(IPPROTO_TCP, arg, port); if (ret < 0) goto err; sct->listen_fds[n] = ret; } } for (n = 0; n < sct->num_listen_fds; n++) { ret = mark_fd_nonblocking(sct->listen_fds[n]); if (ret < 0) goto err; /* child doesn't need the listener */ add_close_on_fork_list(sct->listen_fds[n]); } sct->task = task_register(&(struct task_info) { .name = "server command", .pre_monitor = command_pre_monitor, .post_monitor = command_post_monitor, .context = sct, }, sched); /* * Detect whether the abstract Unix domain socket space is supported, * but do not create the socket. We check this once in server context * so that the command handlers inherit this bit of information and * don't need to check again. */ create_local_socket(NULL); return; err: PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } static int init_afs(int argc, char **argv) { int ret, afs_server_socket[2]; char c; ret = socketpair(PF_UNIX, SOCK_STREAM, 0, afs_server_socket); if (ret < 0) exit(EXIT_FAILURE); afs_pid = fork(); if (afs_pid < 0) exit(EXIT_FAILURE); if (afs_pid == 0) { /* child (afs) */ int i; 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; sprintf(argv[i], "para_server (afs)"); close(afs_server_socket[0]); afs_init(afs_server_socket[1]); } close(afs_server_socket[1]); if (read(afs_server_socket[0], &c, 1) <= 0) { PARA_EMERG_LOG("early afs exit\n"); exit(EXIT_FAILURE); } ret = mark_fd_nonblocking(afs_server_socket[0]); if (ret < 0) exit(EXIT_FAILURE); return afs_server_socket[0]; } static void handle_help_flags(void) { char *help; bool d = OPT_GIVEN(DETAILED_HELP); if (d) help = lls_long_help(CMD_PTR); else if (OPT_GIVEN(HELP)) help = lls_short_help(CMD_PTR); else return; printf("%s\n", help); free(help); exit(EXIT_SUCCESS); } static int server_poll(struct pollfd *fds, nfds_t nfds, int timeout) { static int prev_events = -1; int ret; daemon_set_loglevel(mmd->loglevel); if (prev_events != mmd->events) goto force_update; if (mmd->new_vss_status_flags == mmd->vss_status_flags) goto poll; mmd->events++; force_update: prev_events = mmd->events; mmd->vss_status_flags = mmd->new_vss_status_flags; PARA_DEBUG_LOG("%u events, forcing status update\n", mmd->events); killpg(0, SIGUSR1); poll: mutex_unlock(mmd_mutex); ret = xpoll(fds, nfds, timeout); mutex_lock(mmd_mutex); return ret; } /* exit on errors, never return NULL */ static struct sched *server_init(int argc, char **argv, struct server_command_task *sct) { int ret, daemon_pipe = -1; char *errctx; struct sched *sched; valid_fd_012(); /* parse command line options */ ret = lls(lls_parse(argc, argv, CMD_PTR, &cmdline_lpr, &errctx)); if (ret < 0) goto fail; server_lpr = cmdline_lpr; daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL)); daemon_drop_privileges_or_die(OPT_STRING_VAL(USER), OPT_STRING_VAL(GROUP)); version_handle_flag("server", OPT_GIVEN(VERSION)); handle_help_flags(); parse_config_or_die(false); /* 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, mmd and log mutex */ daemon_set_start_time(); daemon_set_hooks(pre_log_hook, post_log_hook); /* * Although afs uses its own signal handling we must ignore SIGUSR1 * _before_ the afs child process gets born by init_afs() below. It's * racy to do this in the child because the parent might send SIGUSR1 * before the child gets a chance to ignore this signal. * * We also have to block SIGCHLD before the afs process is created * because otherwise para_server does not notice if afs dies before the * SIGCHLD handler has been installed for the parent process by * register_signal_task() below. */ para_sigaction(SIGUSR1, SIG_IGN); para_block_signal(SIGCHLD); PARA_NOTICE_LOG("initializing the audio file selector\n"); sct->afs_fd = init_afs(argc, argv); sched = sched_new(server_poll); register_signal_task(sched); para_unblock_signal(SIGCHLD); ms2tv(OPT_UINT32_VAL(ANNOUNCE_TIME), &announce_tv); PARA_NOTICE_LOG("initializing virtual streaming system\n"); vss_init(sct->afs_fd, sched); register_command_task(sct, sched, argc, argv); if (daemon_pipe >= 0) { if (write(daemon_pipe, "\0", 1) < 0) { PARA_EMERG_LOG("daemon_pipe: %s", strerror(errno)); exit(EXIT_FAILURE); } close(daemon_pipe); } PARA_NOTICE_LOG("server init complete\n"); return sched; fail: assert(ret < 0); if (errctx) PARA_ERROR_LOG("%s\n", errctx); PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } /** * 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. * * \param argc Options are defined in the server lopsub suite. * \param argv Subcommands are defined in the server_command suite. * * We fork once at startup to create the afs process. The child calls \ref * afs_init() of \ref afs.c to initialize the audio file selector. Both * processes define and register their own set of tasks to the scheduler. * * The server processes registers three tasks to handle signals, stream audio, * or dispatch command requests at the command socket. Incoming requests * are handled without blocking by forking and having the child call \ref * handle_connect() of \ref command.c. * * \return EXIT_SUCCESS or EXIT_FAILURE. */ int main(int argc, char *argv[]) { int ret; struct server_command_task server_command_task_struct, *sct = &server_command_task_struct; struct sched *sched = server_init(argc, argv, sct); mutex_lock(mmd_mutex); ret = schedule(sched); /* * We hold the mmd lock: it was re-acquired in server_poll() * after the poll(2) call. */ mutex_unlock(mmd_mutex); sched_shutdown(sched); crypt_shutdown(); 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_cof_list(false /* don't close fds */); if (ret < 0) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); } else { deplete_cof_list(true /* close fds */); ret = handle_connect(sct->client_fd, sct->afs_fd); } vss_shutdown(); shm_detach(mmd); user_list_deplete(); free_lpr(); exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); }