| /* |
| * speechd.c - Speech Dispatcher server program |
| * |
| * Copyright (C) 2001, 2002, 2003, 2006, 2007 Brailcom, o.p.s. |
| * |
| * This is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This software is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| * |
| * $Id: speechd.c,v 1.81 2008-07-10 15:36:49 hanke Exp $ |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <glib.h> |
| #include <gmodule.h> |
| #include <glib/gstdio.h> |
| #include <sys/stat.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <stdint.h> |
| #ifdef USE_LIBSYSTEMD |
| #include <systemd/sd-daemon.h> |
| #endif |
| |
| #ifdef HAVE_SYS_FILIO_H |
| #include <sys/filio.h> /* Needed for FIONREAD on Solaris */ |
| #endif |
| |
| #include "speechd.h" |
| |
| /* Declare dotconf functions and data structures*/ |
| #include "configuration.h" |
| |
| /* Declare functions to allocate and create important data |
| * structures */ |
| #include "alloc.h" |
| #include "sem_functions.h" |
| #include "speaking.h" |
| #include "speak_queue.h" |
| #include "set.h" |
| #include "options.h" |
| #include "server.h" |
| |
| #include <i18n.h> |
| |
| /* list of output modules */ |
| GList *output_modules; |
| |
| /* Manipulating pid files */ |
| int create_pid_file(); |
| void destroy_pid_file(); |
| |
| /* Server socket file descriptor */ |
| int server_socket; |
| |
| GMainLoop *main_loop = NULL; |
| gint server_timeout_source = -1; |
| |
| int client_count = 0; |
| |
| struct SpeechdOptions SpeechdOptions; |
| struct SpeechdStatus SpeechdStatus; |
| |
| pthread_t speak_thread; |
| pthread_mutex_t logging_mutex = PTHREAD_MUTEX_INITIALIZER; |
| pthread_mutex_t element_free_mutex = PTHREAD_MUTEX_INITIALIZER; |
| pthread_mutex_t output_layer_mutex = PTHREAD_MUTEX_INITIALIZER; |
| pthread_mutex_t socket_com_mutex = PTHREAD_MUTEX_INITIALIZER; |
| |
| GHashTable *fd_settings; |
| GHashTable *language_default_modules; |
| GHashTable *fd_uid; |
| |
| TSpeechDQueue *MessageQueue; |
| GList *MessagePausedList; |
| |
| GList *client_specific_settings; |
| |
| GList *last_p5_block; |
| |
| TFDSetElement GlobalFDSet; |
| |
| int speaking_pipe[2]; |
| |
| GHashTable *speechd_sockets_status; |
| |
| FILE *logfile; |
| FILE *custom_logfile; |
| char *custom_log_kind; |
| FILE *debug_logfile; |
| |
| TSpeechDMode spd_mode; |
| |
| static gboolean speechd_client_terminate(gpointer key, gpointer value, gpointer user); |
| static gboolean speechd_reload_dead_modules(gpointer user_data); |
| static gboolean speechd_reload_configuration(gpointer user_data); |
| enum quit_reason { |
| QUIT_SIGINT, |
| QUIT_SIGTERM, |
| QUIT_TIMEOUT, |
| }; |
| static gboolean speechd_quit(gpointer user_data); |
| |
| static gboolean server_process_incoming (gint fd, |
| GIOCondition condition, |
| gpointer data); |
| |
| static gboolean client_process_incoming (gint fd, |
| GIOCondition condition, |
| gpointer data); |
| |
| static void speechd_load_configuration(void); |
| static void speechd_check_modules(void); |
| static void check_client_count(void); |
| |
| #ifndef HAVE_DAEMON |
| /* Added by Willie Walker - daemon is a common, but not universal, extension. |
| */ |
| static int daemon(int nochdir, int noclose) |
| { |
| int fd, i; |
| |
| switch (fork()) { |
| case 0: |
| break; |
| case -1: |
| return -1; |
| default: |
| _exit(0); |
| } |
| |
| if (!nochdir) { |
| chdir("/"); |
| } |
| |
| if (setsid() < 0) { |
| return -1; |
| } |
| |
| if (!noclose) { |
| if (fd = open("/dev/null", O_RDWR) >= 0) { |
| for (i = 0; i < 3; i++) { |
| dup2(fd, i); |
| } |
| if (fd > 2) { |
| close(fd); |
| } |
| } |
| } |
| return 0; |
| } |
| #endif /* HAVE_DAEMON */ |
| |
| /* --- DEBUGGING --- */ |
| |
| /* Just to be able to set breakpoints */ |
| void fatal_error(void) |
| { |
| int i = 0; |
| i++; |
| } |
| |
| /* Logging messages, level of verbosity is defined between 1 and 5, |
| * see documentation */ |
| void MSG2(int level, const char *kind, const char *format, ...) |
| { |
| int std_log = level <= SpeechdOptions.log_level; |
| int custom_log = (kind != NULL && custom_log_kind != NULL && |
| !strcmp(kind, custom_log_kind) && |
| custom_logfile != NULL); |
| |
| if (std_log || custom_log) { |
| va_list args; |
| int i; |
| |
| pthread_mutex_lock(&logging_mutex); |
| |
| { |
| { |
| /* Print timestamp */ |
| time_t t; |
| char tstr[26]; |
| struct timeval tv; |
| t = time(NULL); |
| ctime_r(&t, tstr); |
| gettimeofday(&tv, NULL); |
| /* Remove the trailing \n */ |
| assert(strlen(tstr) > 1); |
| tstr[strlen(tstr) - 1] = 0; |
| if (std_log) { |
| fprintf(logfile, "[%s : %d] speechd: ", |
| tstr, (int)tv.tv_usec); |
| // fprintf(logfile, "[test : %d] speechd: ", |
| // (int) tv.tv_usec); |
| } |
| if (custom_log) { |
| fprintf(custom_logfile, |
| "[%s : %d] speechd: ", tstr, |
| (int)tv.tv_usec); |
| } |
| if (SpeechdOptions.debug) { |
| fprintf(debug_logfile, |
| "[%s : %d] speechd: ", tstr, |
| (int)tv.tv_usec); |
| } |
| } |
| for (i = 1; i < level; i++) { |
| if (std_log) { |
| fprintf(logfile, " "); |
| } |
| if (custom_log) { |
| fprintf(custom_logfile, " "); |
| } |
| } |
| if (std_log) { |
| va_start(args, format); |
| vfprintf(logfile, format, args); |
| va_end(args); |
| fprintf(logfile, "\n"); |
| fflush(logfile); |
| } |
| if (custom_log) { |
| va_start(args, format); |
| vfprintf(custom_logfile, format, args); |
| va_end(args); |
| fprintf(custom_logfile, "\n"); |
| fflush(custom_logfile); |
| } |
| if (SpeechdOptions.debug) { |
| va_start(args, format); |
| vfprintf(debug_logfile, format, args); |
| va_end(args); |
| fprintf(debug_logfile, "\n"); |
| fflush(debug_logfile); |
| } |
| } |
| pthread_mutex_unlock(&logging_mutex); |
| } |
| } |
| |
| /* The main logging function for Speech Dispatcher, |
| level is between -1 and 5. 1 means the most important, |
| 5 less important. Loglevels after 4 can contain private |
| data. -1 logs also to stderr. See Speech Dispatcher |
| documentation */ |
| /* TODO: Define this in terms of MSG somehow. I don't |
| know how to pass '...' arguments to another C function */ |
| void MSG(int level, const char *format, ...) |
| { |
| |
| if ((level <= SpeechdOptions.log_level) |
| || (SpeechdOptions.debug)) { |
| va_list args; |
| int i; |
| pthread_mutex_lock(&logging_mutex); |
| { |
| /* Print timestamp */ |
| { |
| time_t t; |
| char tstr[26]; |
| struct timeval tv; |
| t = time(NULL); |
| ctime_r(&t, tstr); |
| gettimeofday(&tv, NULL); |
| /* Remove the trailing \n */ |
| assert(strlen(tstr) > 1); |
| assert((level >= -1) && (level <= 5)); |
| tstr[strlen(tstr) - 1] = 0; |
| /* Write timestamps */ |
| if (level <= SpeechdOptions.log_level) |
| fprintf(logfile, "[%s : %d] speechd: ", |
| tstr, (int)tv.tv_usec); |
| if (SpeechdOptions.debug) |
| fprintf(debug_logfile, |
| "[%s : %d] speechd: ", tstr, |
| (int)tv.tv_usec); |
| /* fprintf(logfile, "[%s : %d] speechd: ", |
| tstr, (int) tv.tv_usec); */ |
| } |
| |
| for (i = 1; i < level; i++) { |
| fprintf(logfile, " "); |
| } |
| /* Log to ordinary logfile */ |
| if (level <= SpeechdOptions.log_level) { |
| va_start(args, format); |
| vfprintf(logfile, format, args); |
| va_end(args); |
| fprintf(logfile, "\n"); |
| fflush(logfile); |
| } |
| /* Log into debug logfile */ |
| if (SpeechdOptions.debug) { |
| va_start(args, format); |
| vfprintf(debug_logfile, format, args); |
| va_end(args); |
| fprintf(debug_logfile, "\n"); |
| fflush(debug_logfile); |
| } |
| /* Log also into stderr for loglevel -1 */ |
| if (level == -1) { |
| va_start(args, format); |
| vfprintf(stderr, format, args); |
| va_end(args); |
| fprintf(stderr, "\n"); |
| fflush(stderr); |
| } |
| } |
| pthread_mutex_unlock(&logging_mutex); |
| } |
| } |
| |
| /* --- CLIENTS / CONNECTIONS MANAGING --- */ |
| |
| /* Initialize sockets status table */ |
| int speechd_sockets_status_init(void) |
| { |
| speechd_sockets_status = g_hash_table_new_full(g_int_hash, g_int_equal, |
| (GDestroyNotify) g_free, |
| (GDestroyNotify) |
| speechd_socket_free); |
| if (speechd_sockets_status) |
| return 0; |
| else |
| return 1; |
| } |
| |
| /* Register a new socket for SSIP connection */ |
| int speechd_socket_register(int fd) |
| { |
| int *fd_key; |
| TSpeechDSock *speechd_socket; |
| speechd_socket = g_malloc(sizeof(TSpeechDSock)); |
| speechd_socket->o_buf = NULL; |
| speechd_socket->o_bytes = 0; |
| speechd_socket->awaiting_data = 0; |
| speechd_socket->inside_block = 0; |
| fd_key = g_malloc(sizeof(int)); |
| *fd_key = fd; |
| g_hash_table_insert(speechd_sockets_status, fd_key, speechd_socket); |
| return 0; |
| } |
| |
| /* Free a TSpeechDSock structure including it's data */ |
| void speechd_socket_free(TSpeechDSock * speechd_socket) |
| { |
| if (speechd_socket->o_buf) |
| g_string_free(speechd_socket->o_buf, 1); |
| g_free(speechd_socket); |
| } |
| |
| /* Unregister a socket for SSIP communication */ |
| int speechd_socket_unregister(int fd) |
| { |
| return (!g_hash_table_remove(speechd_sockets_status, &fd)); |
| } |
| |
| /* Get a pointer to the TSpeechDSock structure for a given file descriptor */ |
| TSpeechDSock *speechd_socket_get_by_fd(int fd) |
| { |
| return g_hash_table_lookup(speechd_sockets_status, &fd); |
| } |
| |
| /* activity is on server_socket (request for a new connection) */ |
| int speechd_connection_new(int server_socket) |
| { |
| TFDSetElement *new_fd_set; |
| struct sockaddr_in client_address; |
| unsigned int client_len = sizeof(client_address); |
| int client_socket; |
| int *p_client_socket, *p_client_uid, *p_client_uid2; |
| |
| client_socket = |
| accept(server_socket, (struct sockaddr *)&client_address, |
| &client_len); |
| |
| if (client_socket == -1) { |
| MSG(2, |
| "Error: Can't handle connection request of a new client"); |
| return -1; |
| } |
| |
| /* We add the associated client_socket to the descriptor set. */ |
| if (client_socket > SpeechdStatus.max_fd) |
| SpeechdStatus.max_fd = client_socket; |
| MSG(4, "Adding client on fd %d", client_socket); |
| |
| speechd_socket_register(client_socket); |
| |
| /* Create a record in fd_settings */ |
| new_fd_set = (TFDSetElement *) default_fd_set(); |
| if (new_fd_set == NULL) { |
| MSG(2, |
| "Error: Failed to create a record in fd_settings for the new client"); |
| if (SpeechdStatus.max_fd == client_socket) |
| SpeechdStatus.max_fd--; |
| return -1; |
| } |
| new_fd_set->fd = client_socket; |
| new_fd_set->uid = ++SpeechdStatus.max_uid; |
| p_client_socket = (int *)g_malloc(sizeof(int)); |
| p_client_uid = (int *)g_malloc(sizeof(int)); |
| p_client_uid2 = (int *)g_malloc(sizeof(int)); |
| *p_client_socket = client_socket; |
| *p_client_uid = SpeechdStatus.max_uid; |
| *p_client_uid2 = SpeechdStatus.max_uid; |
| |
| g_hash_table_insert(fd_settings, p_client_uid, new_fd_set); |
| g_hash_table_insert(fd_uid, p_client_socket, p_client_uid2); |
| |
| new_fd_set->fd_source = g_unix_fd_add(client_socket, G_IO_IN, client_process_incoming, NULL); |
| |
| MSG(4, "Data structures for client on fd %d created", client_socket); |
| |
| client_count++; |
| check_client_count(); |
| |
| return 0; |
| |
| } |
| |
| int speechd_connection_destroy(int fd) |
| { |
| TFDSetElement *fdset_element; |
| |
| /* Client has gone away and we remove it from the descriptor set. */ |
| MSG(4, "Removing client on fd %d", fd); |
| |
| MSG(4, "Tagging client as inactive in settings"); |
| fdset_element = get_client_settings_by_fd(fd); |
| if (fdset_element != NULL) { |
| fdset_element->fd = -1; |
| fdset_element->active = 0; |
| g_source_remove(fdset_element->fd_source); |
| /* The fdset_element will be freed and removed from the |
| hash table as soon as the client no longer has any |
| message in the queues, check out the speak() function */ |
| } else if (SPEECHD_DEBUG) { |
| DIE("Can't find settings for this client\n"); |
| } |
| |
| MSG(4, "Removing client from the fd->uid table."); |
| |
| g_hash_table_remove(fd_uid, &fd); |
| |
| speechd_socket_unregister(fd); |
| |
| MSG(4, "Closing clients file descriptor %d", fd); |
| |
| if (close(fd) != 0) |
| if (SPEECHD_DEBUG) |
| DIE("Can't close file descriptor associated to this client"); |
| |
| if (fd == SpeechdStatus.max_fd) |
| SpeechdStatus.max_fd--; |
| |
| MSG(4, "Connection closed"); |
| |
| client_count--; |
| check_client_count(); |
| |
| return 0; |
| } |
| |
| static gboolean speechd_client_terminate(gpointer key, gpointer value, gpointer user) |
| { |
| TFDSetElement *set; |
| |
| set = (TFDSetElement *) value; |
| if (set == NULL) { |
| MSG(2, "Error: Empty connection, internal error"); |
| if (SPEECHD_DEBUG) |
| FATAL("Internal error"); |
| return TRUE; |
| } |
| |
| if (set->fd > 0) { |
| MSG(4, "Closing connection on fd %d\n", set->fd); |
| speechd_connection_destroy(set->fd); |
| } |
| mem_free_fdset(set); |
| g_free(set); |
| return TRUE; |
| } |
| |
| /* --- OUTPUT MODULES MANAGING --- */ |
| |
| void speechd_modules_terminate(gpointer data, gpointer user_data) |
| { |
| OutputModule *module; |
| |
| module = (OutputModule *) data; |
| if (module == NULL) { |
| MSG(2, "Error: Empty module, internal error"); |
| return; |
| } |
| unload_output_module(module); |
| |
| return; |
| } |
| |
| void speechd_modules_reload(gpointer data, gpointer user_data) |
| { |
| OutputModule *module; |
| |
| module = (OutputModule *) data; |
| if (module == NULL) { |
| MSG(2, "Empty module, internal error"); |
| return; |
| } |
| |
| reload_output_module(module); |
| |
| return; |
| } |
| |
| void speechd_module_debug(gpointer data, gpointer user_data) |
| { |
| OutputModule *module; |
| |
| module = (OutputModule *) data; |
| if (module == NULL) { |
| MSG(2, "Empty module, internal error"); |
| return; |
| } |
| |
| output_module_debug(module); |
| |
| return; |
| } |
| |
| void speechd_module_nodebug(gpointer data, gpointer user_data) |
| { |
| OutputModule *module; |
| |
| module = (OutputModule *) data; |
| if (module == NULL) { |
| MSG(2, "Empty module, internal error"); |
| return; |
| } |
| |
| output_module_nodebug(module); |
| |
| return; |
| } |
| |
| static gboolean speechd_reload_dead_modules(gpointer user_data) |
| { |
| /* Reload dead modules */ |
| g_list_foreach(output_modules, speechd_modules_reload, NULL); |
| |
| /* Make sure there aren't any more child processes left */ |
| while (waitpid(-1, NULL, WNOHANG) > 0) ; |
| return TRUE; |
| } |
| |
| void speechd_modules_debug(void) |
| { |
| /* Redirect output to debug for all modules */ |
| g_list_foreach(output_modules, speechd_module_debug, NULL); |
| |
| } |
| |
| void speechd_modules_nodebug(void) |
| { |
| /* Redirect output to normal for all modules */ |
| g_list_foreach(output_modules, speechd_module_nodebug, NULL); |
| } |
| |
| /* --- SPEECHD START/EXIT FUNCTIONS --- */ |
| |
| void speechd_options_init(void) |
| { |
| SpeechdOptions.spawn = FALSE; |
| SpeechdOptions.log_level_set = 0; |
| SpeechdOptions.communication_method = NULL; |
| SpeechdOptions.socket_path = NULL; |
| SpeechdOptions.port_set = 0; |
| SpeechdOptions.localhost_access_only_set = 0; |
| SpeechdOptions.pid_file = NULL; |
| SpeechdOptions.conf_file = NULL; |
| SpeechdOptions.conf_dir = NULL; |
| SpeechdOptions.user_conf_dir = NULL; |
| SpeechdOptions.runtime_speechd_dir = NULL; |
| SpeechdOptions.module_dir = MODULEBINDIR; |
| SpeechdOptions.user_module_dir = NULL; |
| SpeechdOptions.log_dir = NULL; |
| SpeechdOptions.log_dir_set = 0; |
| SpeechdOptions.debug = 0; |
| SpeechdOptions.debug_destination = NULL; |
| debug_logfile = NULL; |
| spd_mode = SPD_MODE_DAEMON; |
| } |
| |
| void speechd_init() |
| { |
| SpeechdStatus.max_uid = 0; |
| SpeechdStatus.max_gid = 0; |
| |
| /* Initialize inter-thread comm pipe */ |
| if (pipe(speaking_pipe)) { |
| MSG(1, "Speaking pipe creation failed (%s)", strerror(errno)); |
| FATAL("Can't create pipe"); |
| } |
| |
| /* Initialize Speech Dispatcher priority queue */ |
| MessageQueue = g_malloc0(sizeof(TSpeechDQueue)); |
| if (MessageQueue == NULL) |
| FATAL("Couldn't allocate memory for MessageQueue."); |
| |
| /* Initialize lists */ |
| MessagePausedList = NULL; |
| |
| /* Initialize hash tables */ |
| fd_settings = g_hash_table_new_full(g_int_hash, g_int_equal, |
| (GDestroyNotify) g_free, NULL); |
| assert(fd_settings != NULL); |
| |
| fd_uid = g_hash_table_new_full(g_int_hash, g_int_equal, |
| (GDestroyNotify) g_free, |
| (GDestroyNotify) g_free); |
| assert(fd_uid != NULL); |
| |
| language_default_modules = g_hash_table_new(g_str_hash, g_str_equal); |
| assert(language_default_modules != NULL); |
| |
| speechd_sockets_status_init(); |
| |
| pause_requested = 0; |
| resume_requested = 0; |
| |
| /* Perform some functionality tests */ |
| if (g_module_supported() == FALSE) |
| DIE("Loadable modules not supported by current platform.\n"); |
| |
| if (_POSIX_VERSION < 199506L) |
| DIE("This system doesn't support POSIX.1c threads\n"); |
| |
| /* Fill GlobalFDSet with default values */ |
| GlobalFDSet.min_delay_progress = 2000; |
| |
| /* Initialize list of different client specific settings entries */ |
| client_specific_settings = NULL; |
| |
| if (SpeechdOptions.log_dir == NULL) { |
| SpeechdOptions.log_dir = |
| g_strdup_printf("%s/log/", |
| SpeechdOptions.runtime_speechd_dir); |
| mkdir(SpeechdOptions.log_dir, S_IRWXU); |
| if (!SpeechdOptions.debug_destination) { |
| SpeechdOptions.debug_destination = |
| g_strdup_printf("%s/log/debug", |
| SpeechdOptions.runtime_speechd_dir); |
| mkdir(SpeechdOptions.debug_destination, S_IRWXU); |
| } |
| } |
| |
| /* Load configuration from the config file */ |
| MSG(4, "Reading Speech Dispatcher configuration from %s", |
| SpeechdOptions.conf_file); |
| speechd_load_configuration(); |
| |
| logging_init(); |
| MSG(1, "Speech Dispatcher " VERSION " log start"); |
| |
| #ifndef DARWIN_HOST /* On Darwin we have to delay to after daemon+exec */ |
| module_load_requested_modules(); |
| speechd_check_modules(); |
| #endif |
| |
| last_p5_block = NULL; |
| } |
| |
| static gint modules_compare (gconstpointer a, gconstpointer b) |
| { |
| const char **params_a = (const char **) a; |
| const char *name_a = params_a[0]; |
| const char **params_b = (const char **) b; |
| const char *name_b = params_b[0]; |
| int index_a; |
| int index_b; |
| |
| /* This gives the prioritization order of modules, to automatically select the best quality */ |
| static const char *modules_order[] = { |
| "voxin", |
| "baratinoo", |
| "ivona", |
| "pico", |
| "pico-generic", |
| "rhvoice", |
| "cicero", |
| "kali", |
| "ibmtts", |
| "festival", |
| "flite", |
| "multispeech", |
| "espeak-ng-mbrola", |
| "espeak-ng-mbrola-generic", |
| "espeak-ng", |
| "espeak-mbrola-generic", |
| "espeak", |
| "espeak-generic", |
| }; |
| static const int index_max = sizeof(modules_order) / sizeof(*modules_order); |
| |
| for (index_a = 0; index_a < index_max; index_a++) { |
| if (!strcmp(modules_order[index_a], name_a)) |
| break; |
| } |
| |
| for (index_b = 0; index_b < index_max; index_b++) { |
| if (!strcmp(modules_order[index_b], name_b)) |
| break; |
| } |
| |
| if (index_a != index_max && index_b == index_max) |
| /* We prefer a synth in the list */ |
| return -1; |
| |
| if (index_a == index_max && index_b != index_max) |
| /* We prefer a synth in the list */ |
| return 1; |
| |
| if (index_a != index_max && index_b != index_max) |
| /* We follow the ordering */ |
| return index_a - index_b; |
| |
| /* We fall back to alphabetical ordering */ |
| return strcmp(name_a, name_b); |
| } |
| |
| static void speechd_load_configuration(void) |
| { |
| configfile_t *configfile = NULL; |
| GList *detected_modules = NULL; |
| |
| /* Clean previous configuration */ |
| if (output_modules != NULL) { |
| g_list_foreach(output_modules, speechd_modules_terminate, NULL); |
| g_list_free(output_modules); |
| output_modules = NULL; |
| } |
| |
| /* Make sure there aren't any more child processes left */ |
| while (waitpid(-1, NULL, WNOHANG) > 0) ; |
| |
| /* Load new configuration */ |
| load_default_global_set_options(); |
| |
| spd_num_options = 0; |
| spd_options = load_config_options(&spd_num_options); |
| |
| /* Add the LAST option */ |
| spd_options = add_config_option(spd_options, &spd_num_options, "", 0, |
| NULL, NULL, 0); |
| |
| configfile = |
| dotconf_create(SpeechdOptions.conf_file, spd_options, 0, |
| CASE_INSENSITIVE); |
| if (configfile) { |
| free(configfile->includepath); |
| configfile->includepath = g_strdup(SpeechdOptions.conf_dir); |
| MSG(5, "Config file include path is: %s", |
| configfile->includepath); |
| if (dotconf_command_loop(configfile) == 0) |
| DIE("Error reading config file\n"); |
| dotconf_cleanup(configfile); |
| MSG(2, "Configuration has been read from \"%s\"", |
| SpeechdOptions.conf_file); |
| |
| /* We need to load modules here, since this is called both by speechd_init |
| * and to handle SIGHUP. */ |
| if (module_number_of_requested_modules() < 1) { |
| detected_modules = detect_output_modules(NULL, |
| SpeechdOptions.user_module_dir, |
| SpeechdOptions.user_conf_dir, |
| SpeechdOptions.conf_dir); |
| detected_modules = detect_output_modules(detected_modules, |
| SpeechdOptions.module_dir, |
| SpeechdOptions.user_conf_dir, |
| SpeechdOptions.conf_dir); |
| detected_modules = detect_output_modules(detected_modules, |
| OLDMODULEBINDIR, |
| SpeechdOptions.user_conf_dir, |
| SpeechdOptions.conf_dir); |
| |
| detected_modules = g_list_sort(detected_modules, modules_compare); |
| |
| while (detected_modules != NULL) { |
| char **parameters = detected_modules->data; |
| module_add_load_request(parameters[0], |
| parameters[1], |
| parameters[2], |
| parameters[3], |
| parameters[4], |
| parameters[5]); |
| g_free(detected_modules->data); |
| detected_modules->data = NULL; |
| detected_modules = |
| g_list_delete_link(detected_modules, |
| detected_modules); |
| } |
| } |
| |
| /* Try to make sure we have something that can speak (with no user parameter) */ |
| module_add_load_request( |
| g_strdup("espeak-ng-fallback"), |
| g_strdup("sd_espeak-ng"), |
| g_strdup(""), |
| g_strdup_printf("%s/%s.log", |
| SpeechdOptions.log_dir, |
| "espeak-ng-fallback"), |
| NULL, |
| NULL); |
| |
| /* At worse, tell what is happening */ |
| module_add_load_request( |
| g_strdup("dummy"), |
| g_strdup("sd_dummy"), |
| g_strdup(""), |
| g_strdup_printf("%s/%s.log", |
| SpeechdOptions.log_dir, |
| "dummy"), |
| NULL, |
| NULL); |
| } else { |
| MSG(1, "Can't open %s", SpeechdOptions.conf_file); |
| } |
| |
| free_config_options(spd_options, &spd_num_options); |
| } |
| |
| static gboolean speechd_reload_configuration(gpointer user_data) |
| { |
| speechd_load_configuration(); |
| module_load_requested_modules(); |
| return TRUE; |
| } |
| |
| static void speechd_check_modules(void) |
| { |
| /* Check for output modules */ |
| if (g_list_length(output_modules) == 0) { |
| DIE("No speech output modules were loaded - aborting..."); |
| } else { |
| MSG(3, "Speech Dispatcher started with %d output module%s", |
| g_list_length(output_modules), |
| g_list_length(output_modules) > 1 ? "s" : ""); |
| } |
| } |
| |
| static gboolean speechd_quit(gpointer user_data) |
| { |
| switch ((enum quit_reason)(uintptr_t) user_data) { |
| case QUIT_SIGINT: |
| MSG(4, "Got SIGINT"); |
| break; |
| case QUIT_SIGTERM: |
| MSG(4, "Got SIGTERM"); |
| break; |
| case QUIT_TIMEOUT: |
| MSG(4, "Timed out"); |
| break; |
| default: |
| MSG(4, "Unknown quit reason"); |
| break; |
| } |
| MSG(4, "Quitting main loop"); |
| g_main_loop_quit(main_loop); |
| return FALSE; |
| } |
| |
| /* --- PID FILES --- */ |
| |
| int create_pid_file() |
| { |
| FILE *pid_file; |
| int pid_fd; |
| struct flock lock; |
| int ret; |
| |
| /* If the file exists, examine it's lock */ |
| pid_file = fopen(SpeechdOptions.pid_file, "r"); |
| if (pid_file != NULL) { |
| pid_fd = fileno(pid_file); |
| |
| lock.l_type = F_WRLCK; |
| lock.l_whence = SEEK_SET; |
| lock.l_start = 1; |
| lock.l_len = 3; |
| |
| /* If there is a lock, exit, otherwise remove the old file */ |
| ret = fcntl(pid_fd, F_GETLK, &lock); |
| if (ret == -1) { |
| MSG(-1, |
| "Can't check lock status of an existing pid file.\n"); |
| return -1; |
| } |
| |
| fclose(pid_file); |
| if (lock.l_type != F_UNLCK) { |
| MSG(-1, "Speech Dispatcher already running.\n"); |
| return -1; |
| } |
| |
| unlink(SpeechdOptions.pid_file); |
| } |
| |
| /* Create a new pid file and lock it */ |
| pid_file = fopen(SpeechdOptions.pid_file, "w"); |
| if (pid_file == NULL) { |
| MSG(-1, "Can't create pid file in %s, wrong permissions?\n", |
| SpeechdOptions.pid_file); |
| return -1; |
| } |
| fprintf(pid_file, "%d\n", getpid()); |
| fflush(pid_file); |
| |
| pid_fd = fileno(pid_file); |
| lock.l_type = F_WRLCK; |
| lock.l_whence = SEEK_SET; |
| lock.l_start = 1; |
| lock.l_len = 3; |
| |
| ret = fcntl(pid_fd, F_SETLK, &lock); |
| if (ret == -1) { |
| MSG(-1, "Can't set lock on pid file.\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| void destroy_pid_file(void) |
| { |
| unlink(SpeechdOptions.pid_file); |
| } |
| |
| void logging_init(void) |
| { |
| char *file_name = |
| g_strdup_printf("%s/speech-dispatcher.log", SpeechdOptions.log_dir); |
| assert(file_name != NULL); |
| if (!strncmp(file_name, "stdout", 6)) { |
| logfile = stdout; |
| } else if (!strncmp(file_name, "stderr", 6)) { |
| logfile = stderr; |
| } else { |
| logfile = fopen(file_name, "a"); |
| if (logfile == NULL) { |
| fprintf(stderr, |
| "Error: can't open logging file %s! Using stdout.\n", |
| file_name); |
| logfile = stdout; |
| } else { |
| MSG(3, "Speech Dispatcher "PACKAGE_VERSION" Logging to file %s at level %d", |
| file_name, SpeechdOptions.log_level); |
| } |
| } |
| |
| if (!debug_logfile) |
| debug_logfile = stdout; |
| |
| g_free(file_name); |
| return; |
| } |
| |
| /* --- Sockets --- */ |
| |
| int make_local_socket(const char *filename) |
| { |
| struct sockaddr_un name; |
| int sock; |
| size_t size; |
| |
| /* Create the socket. */ |
| sock = socket(AF_UNIX, SOCK_STREAM, 0); |
| if (sock < 0) { |
| FATAL("Can't create local socket"); |
| } |
| |
| /* Bind a name to the socket. */ |
| name.sun_family = AF_UNIX; |
| strncpy(name.sun_path, filename, sizeof(name.sun_path)); |
| name.sun_path[sizeof(name.sun_path) - 1] = '\0'; |
| size = SUN_LEN(&name); |
| |
| if (bind(sock, (struct sockaddr *)&name, size) < 0) { |
| FATAL("Can't bind local socket"); |
| } |
| |
| if (listen(sock, 50) == -1) { |
| MSG(2, "listen failed: ERRNO:%s", strerror(errno)); |
| FATAL("listen() failed for local socket"); |
| } |
| |
| return sock; |
| } |
| |
| int make_inet_socket(const int port) |
| { |
| struct sockaddr_in server_address; |
| int server_socket; |
| |
| /* Create an inet socket */ |
| server_socket = socket(AF_INET, SOCK_STREAM, 0); |
| if (server_socket < 0) { |
| FATAL("Can't create inet socket"); |
| } |
| |
| /* Set REUSEADDR flag */ |
| const int flag = 1; |
| if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &flag, |
| sizeof(int))) |
| MSG(2, "Error: Setting socket option failed!"); |
| |
| server_address.sin_family = AF_INET; |
| |
| /* Enable access only to localhost or for any address |
| based on LocalhostAccessOnly configuration option. */ |
| if (SpeechdOptions.localhost_access_only) |
| server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| else |
| server_address.sin_addr.s_addr = htonl(INADDR_ANY); |
| |
| server_address.sin_port = htons(port); |
| |
| MSG(4, "Opening inet socket connection"); |
| if (bind(server_socket, (struct sockaddr *)&server_address, |
| sizeof(server_address)) == -1) { |
| MSG(-1, "bind() failed: %s", strerror(errno)); |
| FATAL("Couldn't open inet socket, try a few minutes later."); |
| } |
| |
| if (listen(server_socket, 50) == -1) { |
| MSG(2, "ERRNO:%s", strerror(errno)); |
| FATAL |
| ("listen() failed for inet socket, another Speech Dispatcher running?"); |
| } |
| |
| return server_socket; |
| } |
| |
| gboolean server_process_incoming (gint fd, |
| GIOCondition condition, |
| gpointer data) |
| { |
| int ret; |
| |
| ret = speechd_connection_new(fd); |
| if (ret != 0) { |
| MSG(2, "Error: Failed to add new client!"); |
| if (SPEECHD_DEBUG) { |
| FATAL("Failed to add new client"); |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| gboolean client_process_incoming (gint fd, |
| GIOCondition condition, |
| gpointer data) |
| { |
| int ret; |
| int nread; |
| |
| ioctl(fd, FIONREAD, &nread); |
| |
| if (nread == 0) { |
| /* client has gone */ |
| ret = speechd_connection_destroy(fd); |
| if (ret != 0) { |
| MSG(2, "Error: Failed to close the client!"); |
| } |
| return FALSE; |
| } |
| |
| /* client sends some commands or data */ |
| if (serve(fd) == -1) { |
| MSG(2, "Error: Failed to serve client on fd %d!", fd); |
| } |
| |
| return TRUE; |
| } |
| |
| static void check_client_count(void) |
| { |
| if (client_count <= 0 |
| && SpeechdOptions.server_timeout > 0) { |
| MSG(4, "Currently no clients connected, enabling shutdown timer."); |
| server_timeout_source = g_timeout_add_seconds( |
| SpeechdOptions.server_timeout, |
| speechd_quit, (void*) (uintptr_t) QUIT_TIMEOUT); |
| } else { |
| if (server_timeout_source >= 0) { |
| MSG(4, "Clients connected, disabling shutdown timer."); |
| g_source_remove(server_timeout_source); |
| server_timeout_source = -1; |
| } |
| } |
| } |
| |
| /* --- MAIN --- */ |
| |
| int main(int argc, char *argv[]) |
| { |
| int ret; |
| /* Autospawn helper variables */ |
| char *spawn_communication_method = NULL; |
| int spawn_port = 0; |
| char *spawn_socket_path = NULL; |
| |
| /* Strip all permissions for 'others' from the files created */ |
| umask(007); |
| |
| /* Initialize logging */ |
| logfile = stdout; |
| SpeechdOptions.log_level = 1; |
| custom_logfile = NULL; |
| custom_log_kind = NULL; |
| |
| /* initialize i18n support */ |
| i18n_init(); |
| |
| speechd_options_init(); |
| |
| options_parse(argc, argv); |
| |
| if (SpeechdOptions.spawn) { |
| /* In case of --spawn, copy the host port and socket_path |
| parameters into temporary spawn_ variables for later comparison |
| with the config file and unset them */ |
| if (SpeechdOptions.communication_method_set) { |
| spawn_communication_method = |
| g_strdup(SpeechdOptions.communication_method); |
| g_free(SpeechdOptions.communication_method); |
| SpeechdOptions.communication_method_set = 0; |
| } |
| if (SpeechdOptions.port_set) { |
| spawn_port = SpeechdOptions.port; |
| SpeechdOptions.port_set = 0; |
| } |
| if (SpeechdOptions.socket_path_set) { |
| spawn_socket_path = |
| g_strdup(SpeechdOptions.socket_path); |
| g_free(SpeechdOptions.socket_path); |
| SpeechdOptions.socket_path_set = 0; |
| } |
| } |
| |
| MSG(1, "Speech Dispatcher " VERSION " starting"); |
| |
| /* By default, search for configuration options in $XDG_CONFIG_HOME/speech-dispatcher |
| and sockets and pid files in $XDG_RUNTIME_DIR/speech-dispatcher */ |
| { |
| const char *user_runtime_dir; |
| const char *user_config_dir; |
| const char *user_data_dir; |
| |
| user_runtime_dir = g_get_user_runtime_dir(); |
| user_config_dir = g_get_user_config_dir(); |
| user_data_dir = g_get_user_data_dir(); |
| |
| /* Setup a speechd-dispatcher directory or create a new one */ |
| SpeechdOptions.runtime_speechd_dir = |
| g_strdup_printf("%s/speech-dispatcher", user_runtime_dir); |
| MSG(4, "Trying to find %s", SpeechdOptions.runtime_speechd_dir); |
| g_mkdir_with_parents(SpeechdOptions.runtime_speechd_dir, |
| S_IRWXU); |
| MSG(4, "Using directory: %s for pidfile and logging", |
| SpeechdOptions.runtime_speechd_dir); |
| /* Pidfile */ |
| if (SpeechdOptions.pid_file == NULL) { |
| /* If no pidfile path specified on command line, use default local dir */ |
| SpeechdOptions.pid_file = |
| g_strdup_printf("%s/pid/speech-dispatcher.pid", |
| SpeechdOptions.runtime_speechd_dir); |
| gchar *dirname = g_path_get_dirname(SpeechdOptions.pid_file); |
| g_mkdir(dirname, S_IRWXU); |
| g_free(dirname); |
| } |
| /* Config file */ |
| if (SpeechdOptions.conf_dir) { |
| SpeechdOptions.conf_file = |
| g_strdup_printf("%s/speechd.conf", SpeechdOptions.conf_dir); |
| } else { |
| /* If no conf_dir was specified on command line, try default local config dir */ |
| if (strcmp(SYS_CONF, "")) |
| SpeechdOptions.conf_dir = |
| g_strdup(SYS_CONF); |
| else |
| SpeechdOptions.conf_dir = |
| g_strdup("/etc/speech-dispatcher/"); |
| SpeechdOptions.user_conf_dir = |
| g_build_filename(user_config_dir, |
| "speech-dispatcher", NULL); |
| |
| MSG(4, "Looking for configuration in %s", SpeechdOptions.user_conf_dir); |
| SpeechdOptions.conf_file = |
| g_strdup_printf("%s/speechd.conf", SpeechdOptions.user_conf_dir); |
| if (!g_file_test |
| (SpeechdOptions.conf_file, G_FILE_TEST_IS_REGULAR)) { |
| /* If the local configuration file doesn't exist, read the global configuration */ |
| g_free(SpeechdOptions.conf_file); |
| SpeechdOptions.conf_file = |
| g_strdup_printf("%s/speechd.conf", SpeechdOptions.conf_dir); |
| } |
| } |
| |
| SpeechdOptions.user_module_dir = |
| g_strdup_printf("%s/../libexec/speech-dispatcher-modules", user_data_dir); |
| MSG(4, "User module dir is %s", SpeechdOptions.user_module_dir); |
| } |
| |
| /* Check for PID file or create a new one or exit if Speech Dispatcher |
| is already running */ |
| if (create_pid_file() != 0) |
| exit(1); |
| |
| /* Handle --spawn request */ |
| if (SpeechdOptions.spawn) { |
| /* Check whether spawning is not disabled */ |
| gchar *config_contents; |
| int err; |
| GRegex *regexp; |
| int result; |
| |
| err = |
| g_file_get_contents(SpeechdOptions.conf_file, |
| &config_contents, NULL, NULL); |
| if (err == FALSE) { |
| MSG(-1, "Error opening %s", SpeechdOptions.conf_file); |
| FATAL("Can't open conf file"); |
| } |
| regexp = |
| g_regex_new("^[ ]*DisableAutoSpawn", G_REGEX_MULTILINE, 0, |
| NULL); |
| result = g_regex_match(regexp, config_contents, 0, NULL); |
| if (result) { |
| MSG(-1, |
| "Autospawn requested but disabled in configuration"); |
| exit(1); |
| } |
| g_free(config_contents); |
| g_regex_unref(regexp); |
| MSG(2, "Starting Speech Dispatcher due to auto-spawn"); |
| } |
| |
| speechd_init(); |
| |
| /* Handle socket_path 'default' */ |
| // TODO: This is a hack, we should do that at appropriate places... |
| if (!strcmp(SpeechdOptions.socket_path, "default")) { |
| /* This code cannot be moved above next to conf_dir and pidpath resolution because |
| we need to also consider the DotConf configuration, which is read in speechd_init() */ |
| GString *socket_filename; |
| socket_filename = g_string_new(""); |
| if (SpeechdOptions.runtime_speechd_dir) { |
| g_string_printf(socket_filename, "%s/speechd.sock", |
| SpeechdOptions.runtime_speechd_dir); |
| } else { |
| FATAL |
| ("Socket name file not set and user has no runtime directory"); |
| } |
| g_free(SpeechdOptions.socket_path); |
| SpeechdOptions.socket_path = g_strdup(socket_filename->str); |
| g_string_free(socket_filename, 1); |
| } |
| |
| /* Check if the communication method corresponds to the spawn request */ |
| /* TODO: This should preferably be done much sooner, but the current |
| configuration mechanism doesn't allow it */ |
| if (SpeechdOptions.spawn) { |
| if (spawn_communication_method) { |
| if (strcmp |
| (spawn_communication_method, |
| SpeechdOptions.communication_method)) { |
| MSG(-1, |
| "Autospawn failed: Mismatch in communication methods. Client " |
| "requests %s, most probably due to its configuration or the value of " |
| "the SPEECHD_ADDRESS environment variable, but the server is configured " |
| "to provide the %s method.", |
| spawn_communication_method, |
| SpeechdOptions.communication_method); |
| exit(1); |
| } else { |
| if (!strcmp |
| (SpeechdOptions.communication_method, |
| "inet_socket")) { |
| /* Check port */ |
| if (spawn_port != 0) |
| if (spawn_port != |
| SpeechdOptions.port) { |
| MSG(-1, |
| "Autospawn failed: Mismatch in port numbers. Server " |
| "is configured to use the inet_socket method on the port %d " |
| "while the client requests port %d, most probably due to its " |
| "configuration or the value of the SPEECHD_ADDRESS environment " |
| "variable.", |
| SpeechdOptions.port, |
| spawn_port); |
| exit(1); |
| } |
| } else if (!strcmp |
| (SpeechdOptions.communication_method, |
| "unix_socket")) { |
| /* Check socket name */ |
| if (spawn_socket_path) |
| { |
| char *spawn_socket_path_norm = realpath(spawn_socket_path, NULL); |
| char *opts_socket_path_norm = realpath(SpeechdOptions.socket_path, NULL); |
| if (spawn_socket_path_norm && opts_socket_path_norm |
| && strcmp(spawn_socket_path_norm, opts_socket_path_norm)) |
| { |
| MSG(-1, |
| "Autospawn failed: Mismatch in socket names. The server " |
| "is configured to provide a socket interface in %s, but the " |
| "client requests a different path: %s. This is most probably " |
| "due to the client application configuration or the value of " |
| "the SPEECHD_ADDRESS environment variable.", |
| SpeechdOptions.socket_path, |
| spawn_socket_path); |
| exit(1); |
| } |
| free(spawn_socket_path_norm); |
| free(opts_socket_path_norm); |
| } |
| } else |
| assert(0); |
| } |
| } |
| g_free(spawn_communication_method); |
| g_free(spawn_socket_path); |
| } |
| |
| char *sock_fd = getenv("SPEECHD_SOCK_FD"); |
| if (sock_fd) { |
| server_socket = atoi(sock_fd); |
| } |
| #ifdef USE_LIBSYSTEMD |
| else if (sd_listen_fds(0) >= 1) { |
| MSG(4, "Daemon launched via Systemd socket activation"); |
| server_socket = SD_LISTEN_FDS_START; |
| } |
| #endif |
| else if (!strcmp(SpeechdOptions.communication_method, "inet_socket")) { |
| MSG(4, "Speech Dispatcher will use inet port %d", |
| SpeechdOptions.port); |
| /* Connect and start listening on inet socket */ |
| server_socket = make_inet_socket(SpeechdOptions.port); |
| } else if (!strcmp(SpeechdOptions.communication_method, "unix_socket")) { |
| /* Determine appropariate socket file name */ |
| MSG(4, "Speech Dispatcher will use local unix socket: %s", |
| SpeechdOptions.socket_path); |
| /* Delete an old socket file if it exists */ |
| if (g_file_test(SpeechdOptions.socket_path, G_FILE_TEST_EXISTS)) |
| { |
| if (g_unlink(SpeechdOptions.socket_path) == -1) |
| FATAL |
| ("Local socket file exists but impossible to delete. Wrong permissions?"); |
| else |
| MSG(4, "Deleted old socket\n"); |
| } |
| /* Connect and start listening on local unix socket */ |
| server_socket = make_local_socket(SpeechdOptions.socket_path); |
| } else { |
| FATAL("Unknown communication method"); |
| } |
| |
| /* Fork, set uid, chdir, etc. */ |
| if (spd_mode == SPD_MODE_DAEMON) { |
| MSG(4, "Daemon mode, forking\n"); |
| if (daemon(0, 0)) { |
| FATAL("Can't fork child process"); |
| } |
| /* Re-create the pid file under this process */ |
| unlink(SpeechdOptions.pid_file); |
| if (create_pid_file() == -1) |
| return -1; |
| |
| #ifdef DARWIN_HOST |
| /* On Darwin, libao sound will break if just forking, we have to re-exec, see |
| * https://developer.apple.com/library/archive/technotes/tn2083/_index.html#//apple_ref/doc/uid/DTS10003794-CH1-SUBSUBSECTION66 |
| */ |
| char *args[argc+2]; |
| memcpy(args, argv, argc * sizeof *argv); |
| args[argc] = "-s"; |
| args[argc+1] = NULL; |
| char sock[64]; |
| sprintf(sock, "SPEECHD_SOCK_FD=%d", server_socket); |
| putenv(sock); |
| if (execv(BINDIR "/speech-dispatcher", args)) |
| FATAL("Can't exec child process"); |
| #endif |
| } |
| |
| #ifdef DARWIN_HOST |
| module_load_requested_modules(); |
| speechd_check_modules(); |
| #endif |
| |
| /* Set up the main loop and register signals */ |
| main_loop = g_main_loop_new(g_main_context_default(), FALSE); |
| g_unix_signal_add(SIGINT, speechd_quit, (void*) (uintptr_t) QUIT_SIGINT); |
| g_unix_signal_add(SIGTERM, speechd_quit, (void*) (uintptr_t) QUIT_SIGTERM); |
| g_unix_signal_add(SIGHUP, speechd_reload_configuration, NULL); |
| g_unix_signal_add(SIGUSR1, speechd_reload_dead_modules, NULL); |
| (void)signal(SIGPIPE, SIG_IGN); |
| |
| MSG(4, "Creating new thread for speak()"); |
| ret = pthread_create(&speak_thread, NULL, speak, NULL); |
| if (ret != 0) |
| FATAL("Speak thread failed!\n"); |
| |
| char *status; |
| ret = module_speak_queue_init(SpeechdOptions.max_queue_size, &status); |
| if (ret != 0) |
| FATAL("Speak queue thread failed: %s!\n", status); |
| |
| SpeechdStatus.max_fd = server_socket; |
| |
| g_unix_fd_add(server_socket, G_IO_IN, |
| server_process_incoming, NULL); |
| |
| /* Now wait for clients and requests. */ |
| MSG(1, "Speech Dispatcher started and waiting for clients ..."); |
| |
| check_client_count(); |
| |
| g_main_loop_run(main_loop); |
| |
| MSG(1, "Terminating..."); |
| |
| MSG(2, "Closing open connections..."); |
| /* We will browse through all the connections and close them. */ |
| g_hash_table_foreach_remove(fd_settings, speechd_client_terminate, |
| NULL); |
| g_hash_table_destroy(fd_settings); |
| |
| MSG(4, "Closing speak() thread..."); |
| ret = pthread_cancel(speak_thread); |
| if (ret != 0) |
| FATAL("Speak thread failed to cancel!\n"); |
| |
| ret = pthread_join(speak_thread, NULL); |
| if (ret != 0) |
| FATAL("Speak thread failed to join!\n"); |
| |
| MSG(4, "Closing play() thread..."); |
| module_speak_queue_terminate(); |
| |
| MSG(2, "Closing open output modules..."); |
| /* Call the close() function of each registered output module. */ |
| g_list_foreach(output_modules, speechd_modules_terminate, NULL); |
| g_list_free(output_modules); |
| |
| MSG(2, "Closing server connection..."); |
| if (close(server_socket) == -1) |
| MSG(2, "close() failed: %s", strerror(errno)); |
| |
| MSG(4, "Removing pid file"); |
| destroy_pid_file(); |
| |
| fflush(NULL); |
| |
| g_main_loop_unref(main_loop); |
| main_loop = NULL; |
| |
| MSG(2, "Speech Dispatcher terminated correctly"); |
| |
| exit(0); |
| } |
| |
| void check_locked(pthread_mutex_t * lock) |
| { |
| if (pthread_mutex_trylock(lock) == 0) { |
| MSG(1, |
| "CRITICAL ERROR: Not locked but accessing structure data!"); |
| fprintf(stderr, "WARNING! WARNING! MUTEX CHECK FAILED!\n"); |
| fflush(stderr); |
| exit(0); |
| } |
| } |