1 /*
2 * speechd.c - Speech Dispatcher server program
3 *
4 * Copyright (C) 2001, 2002, 2003, 2006, 2007 Brailcom, o.p.s.
5 *
6 * This is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
9 * any later version.
10 *
11 * This software is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 *
19 * $Id: speechd.c,v 1.81 2008-07-10 15:36:49 hanke Exp $
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <glib.h>
27 #include <gmodule.h>
28 #include <glib/gstdio.h>
29 #include <sys/stat.h>
30 #include <sys/socket.h>
31 #include <sys/un.h>
32
33 #ifdef HAVE_SYS_FILIO_H
34 #include <sys/filio.h> /* Needed for FIONREAD on Solaris */
35 #endif
36
37 #include "speechd.h"
38
39 /* Declare dotconf functions and data structures*/
40 #include "configuration.h"
41
42 /* Declare functions to allocate and create important data
43 * structures */
44 #include "alloc.h"
45 #include "sem_functions.h"
46 #include "speaking.h"
47 #include "set.h"
48 #include "options.h"
49 #include "server.h"
50
51 #include <i18n.h>
52
53 /* list of output modules */
54 GList *output_modules;
55
56 /* Manipulating pid files */
57 int create_pid_file();
58 void destroy_pid_file();
59
60 /* Server socket file descriptor */
61 int server_socket;
62
63 GMainLoop *main_loop = NULL;
64 gint server_timeout_source = -1;
65
66 int client_count = 0;
67
68 struct SpeechdOptions SpeechdOptions;
69 struct SpeechdStatus SpeechdStatus;
70
71 pthread_t speak_thread;
72 pthread_mutex_t logging_mutex;
73 pthread_mutex_t element_free_mutex;
74 pthread_mutex_t output_layer_mutex;
75 pthread_mutex_t socket_com_mutex;
76
77 GHashTable *fd_settings;
78 GHashTable *language_default_modules;
79 GHashTable *fd_uid;
80
81 TSpeechDQueue *MessageQueue;
82 GList *MessagePausedList;
83
84 GList *client_specific_settings;
85
86 GList *last_p5_block;
87
88 TFDSetElement GlobalFDSet;
89
90 int speaking_pipe[2];
91
92 GHashTable *speechd_sockets_status;
93
94 FILE *logfile;
95 FILE *custom_logfile;
96 char *custom_log_kind;
97 FILE *debug_logfile;
98
99 TSpeechDMode spd_mode;
100
101 static gboolean speechd_client_terminate(gpointer key, gpointer value, gpointer user);
102 static gboolean speechd_reload_dead_modules(gpointer user_data);
103 static gboolean speechd_load_configuration(gpointer user_data);
104 static gboolean speechd_quit(gpointer user_data);
105
106 static gboolean server_process_incoming (gint fd,
107 GIOCondition condition,
108 gpointer data);
109
110 static gboolean client_process_incoming (gint fd,
111 GIOCondition condition,
112 gpointer data);
113
114 void check_client_count(void);
115
116 #ifndef HAVE_DAEMON
117 /* Added by Willie Walker - daemon is a common, but not universal, extension.
118 */
daemon(int nochdir,int noclose)119 static int daemon(int nochdir, int noclose)
120 {
121 int fd, i;
122
123 switch (fork()) {
124 case 0:
125 break;
126 case -1:
127 return -1;
128 default:
129 _exit(0);
130 }
131
132 if (!nochdir) {
133 chdir("/");
134 }
135
136 if (setsid() < 0) {
137 return -1;
138 }
139
140 if (!noclose) {
141 if (fd = open("/dev/null", O_RDWR) >= 0) {
142 for (i = 0; i < 3; i++) {
143 dup2(fd, i);
144 }
145 if (fd > 2) {
146 close(fd);
147 }
148 }
149 }
150 return 0;
151 }
152 #endif /* HAVE_DAEMON */
153
154 /* --- DEBUGGING --- */
155
156 /* Just to be able to set breakpoints */
fatal_error(void)157 void fatal_error(void)
158 {
159 int i = 0;
160 i++;
161 }
162
163 /* Logging messages, level of verbosity is defined between 1 and 5,
164 * see documentation */
MSG2(int level,char * kind,char * format,...)165 void MSG2(int level, char *kind, char *format, ...)
166 {
167 int std_log = level <= SpeechdOptions.log_level;
168 int custom_log = (kind != NULL && custom_log_kind != NULL &&
169 !strcmp(kind, custom_log_kind) &&
170 custom_logfile != NULL);
171
172 if (std_log || custom_log) {
173 va_list args;
174 int i;
175
176 pthread_mutex_lock(&logging_mutex);
177
178 {
179 {
180 /* Print timestamp */
181 time_t t;
182 char *tstr;
183 struct timeval tv;
184 t = time(NULL);
185 tstr = g_strdup(ctime(&t));
186 gettimeofday(&tv, NULL);
187 assert(tstr);
188 /* Remove the trailing \n */
189 assert(strlen(tstr) > 1);
190 tstr[strlen(tstr) - 1] = 0;
191 if (std_log) {
192 fprintf(logfile, "[%s : %d] speechd: ",
193 tstr, (int)tv.tv_usec);
194 // fprintf(logfile, "[test : %d] speechd: ",
195 // (int) tv.tv_usec);
196 }
197 if (custom_log) {
198 fprintf(custom_logfile,
199 "[%s : %d] speechd: ", tstr,
200 (int)tv.tv_usec);
201 }
202 if (SpeechdOptions.debug) {
203 fprintf(debug_logfile,
204 "[%s : %d] speechd: ", tstr,
205 (int)tv.tv_usec);
206 }
207 g_free(tstr);
208 }
209 for (i = 1; i < level; i++) {
210 if (std_log) {
211 fprintf(logfile, " ");
212 }
213 if (custom_log) {
214 fprintf(custom_logfile, " ");
215 }
216 }
217 if (std_log) {
218 va_start(args, format);
219 vfprintf(logfile, format, args);
220 va_end(args);
221 fprintf(logfile, "\n");
222 fflush(logfile);
223 }
224 if (custom_log) {
225 va_start(args, format);
226 vfprintf(custom_logfile, format, args);
227 va_end(args);
228 fprintf(custom_logfile, "\n");
229 fflush(custom_logfile);
230 }
231 if (SpeechdOptions.debug) {
232 va_start(args, format);
233 vfprintf(debug_logfile, format, args);
234 va_end(args);
235 fprintf(debug_logfile, "\n");
236 fflush(debug_logfile);
237 }
238 }
239 pthread_mutex_unlock(&logging_mutex);
240 }
241 }
242
243 /* The main logging function for Speech Dispatcher,
244 level is between -1 and 5. 1 means the most important,
245 5 less important. Loglevels after 4 can contain private
246 data. -1 logs also to stderr. See Speech Dispatcher
247 documentation */
248 /* TODO: Define this in terms of MSG somehow. I don't
249 know how to pass '...' arguments to another C function */
MSG(int level,char * format,...)250 void MSG(int level, char *format, ...)
251 {
252
253 if ((level <= SpeechdOptions.log_level)
254 || (SpeechdOptions.debug)) {
255 va_list args;
256 int i;
257 pthread_mutex_lock(&logging_mutex);
258 {
259 /* Print timestamp */
260 {
261 time_t t;
262 char *tstr;
263 struct timeval tv;
264 t = time(NULL);
265 tstr = g_strdup(ctime(&t));
266 gettimeofday(&tv, NULL);
267 /* Remove the trailing \n */
268 assert(tstr);
269 assert(strlen(tstr) > 1);
270 assert((level >= -1) && (level <= 5));
271 tstr[strlen(tstr) - 1] = 0;
272 /* Write timestamps */
273 if (level <= SpeechdOptions.log_level)
274 fprintf(logfile, "[%s : %d] speechd: ",
275 tstr, (int)tv.tv_usec);
276 if (SpeechdOptions.debug)
277 fprintf(debug_logfile,
278 "[%s : %d] speechd: ", tstr,
279 (int)tv.tv_usec);
280 /* fprintf(logfile, "[%s : %d] speechd: ",
281 tstr, (int) tv.tv_usec); */
282 g_free(tstr);
283 }
284
285 for (i = 1; i < level; i++) {
286 fprintf(logfile, " ");
287 }
288 /* Log to ordinary logfile */
289 if (level <= SpeechdOptions.log_level) {
290 va_start(args, format);
291 vfprintf(logfile, format, args);
292 va_end(args);
293 fprintf(logfile, "\n");
294 fflush(logfile);
295 }
296 /* Log into debug logfile */
297 if (SpeechdOptions.debug) {
298 va_start(args, format);
299 vfprintf(debug_logfile, format, args);
300 va_end(args);
301 fprintf(debug_logfile, "\n");
302 fflush(debug_logfile);
303 }
304 /* Log also into stderr for loglevel -1 */
305 if (level == -1) {
306 va_start(args, format);
307 vfprintf(stderr, format, args);
308 va_end(args);
309 fprintf(stderr, "\n");
310 fflush(stderr);
311 }
312 }
313 pthread_mutex_unlock(&logging_mutex);
314 }
315 }
316
317 /* --- CLIENTS / CONNECTIONS MANAGING --- */
318
319 /* Initialize sockets status table */
speechd_sockets_status_init(void)320 int speechd_sockets_status_init(void)
321 {
322 speechd_sockets_status = g_hash_table_new_full(g_int_hash, g_int_equal,
323 (GDestroyNotify) g_free,
324 (GDestroyNotify)
325 speechd_socket_free);
326 if (speechd_sockets_status)
327 return 0;
328 else
329 return 1;
330 }
331
332 /* Register a new socket for SSIP connection */
speechd_socket_register(int fd)333 int speechd_socket_register(int fd)
334 {
335 int *fd_key;
336 TSpeechDSock *speechd_socket;
337 speechd_socket = g_malloc(sizeof(TSpeechDSock));
338 speechd_socket->o_buf = NULL;
339 speechd_socket->o_bytes = 0;
340 speechd_socket->awaiting_data = 0;
341 speechd_socket->inside_block = 0;
342 fd_key = g_malloc(sizeof(int));
343 *fd_key = fd;
344 g_hash_table_insert(speechd_sockets_status, fd_key, speechd_socket);
345 return 0;
346 }
347
348 /* Free a TSpeechDSock structure including it's data */
speechd_socket_free(TSpeechDSock * speechd_socket)349 void speechd_socket_free(TSpeechDSock * speechd_socket)
350 {
351 if (speechd_socket->o_buf)
352 g_string_free(speechd_socket->o_buf, 1);
353 g_free(speechd_socket);
354 }
355
356 /* Unregister a socket for SSIP communication */
speechd_socket_unregister(int fd)357 int speechd_socket_unregister(int fd)
358 {
359 return (!g_hash_table_remove(speechd_sockets_status, &fd));
360 }
361
362 /* Get a pointer to the TSpeechDSock structure for a given file descriptor */
speechd_socket_get_by_fd(int fd)363 TSpeechDSock *speechd_socket_get_by_fd(int fd)
364 {
365 return g_hash_table_lookup(speechd_sockets_status, &fd);
366 }
367
368 /* activity is on server_socket (request for a new connection) */
speechd_connection_new(int server_socket)369 int speechd_connection_new(int server_socket)
370 {
371 TFDSetElement *new_fd_set;
372 struct sockaddr_in client_address;
373 unsigned int client_len = sizeof(client_address);
374 int client_socket;
375 int *p_client_socket, *p_client_uid, *p_client_uid2;
376
377 client_socket =
378 accept(server_socket, (struct sockaddr *)&client_address,
379 &client_len);
380
381 if (client_socket == -1) {
382 MSG(2,
383 "Error: Can't handle connection request of a new client");
384 return -1;
385 }
386
387 /* We add the associated client_socket to the descriptor set. */
388 if (client_socket > SpeechdStatus.max_fd)
389 SpeechdStatus.max_fd = client_socket;
390 MSG(4, "Adding client on fd %d", client_socket);
391
392 speechd_socket_register(client_socket);
393
394 /* Create a record in fd_settings */
395 new_fd_set = (TFDSetElement *) default_fd_set();
396 if (new_fd_set == NULL) {
397 MSG(2,
398 "Error: Failed to create a record in fd_settings for the new client");
399 if (SpeechdStatus.max_fd == client_socket)
400 SpeechdStatus.max_fd--;
401 return -1;
402 }
403 new_fd_set->fd = client_socket;
404 new_fd_set->uid = ++SpeechdStatus.max_uid;
405 p_client_socket = (int *)g_malloc(sizeof(int));
406 p_client_uid = (int *)g_malloc(sizeof(int));
407 p_client_uid2 = (int *)g_malloc(sizeof(int));
408 *p_client_socket = client_socket;
409 *p_client_uid = SpeechdStatus.max_uid;
410 *p_client_uid2 = SpeechdStatus.max_uid;
411
412 g_hash_table_insert(fd_settings, p_client_uid, new_fd_set);
413 g_hash_table_insert(fd_uid, p_client_socket, p_client_uid2);
414
415 new_fd_set->fd_source = g_unix_fd_add(client_socket, G_IO_IN, client_process_incoming, NULL);
416
417 MSG(4, "Data structures for client on fd %d created", client_socket);
418
419 client_count++;
420 check_client_count();
421
422 return 0;
423
424 }
425
speechd_connection_destroy(int fd)426 int speechd_connection_destroy(int fd)
427 {
428 TFDSetElement *fdset_element;
429
430 /* Client has gone away and we remove it from the descriptor set. */
431 MSG(4, "Removing client on fd %d", fd);
432
433 MSG(4, "Tagging client as inactive in settings");
434 fdset_element = get_client_settings_by_fd(fd);
435 if (fdset_element != NULL) {
436 fdset_element->fd = -1;
437 fdset_element->active = 0;
438 g_source_remove(fdset_element->fd_source);
439 /* The fdset_element will be freed and removed from the
440 hash table as soon as the client no longer has any
441 message in the queues, check out the speak() function */
442 } else if (SPEECHD_DEBUG) {
443 DIE("Can't find settings for this client\n");
444 }
445
446 MSG(4, "Removing client from the fd->uid table.");
447
448 g_hash_table_remove(fd_uid, &fd);
449
450 speechd_socket_unregister(fd);
451
452 MSG(4, "Closing clients file descriptor %d", fd);
453
454 if (close(fd) != 0)
455 if (SPEECHD_DEBUG)
456 DIE("Can't close file descriptor associated to this client");
457
458 if (fd == SpeechdStatus.max_fd)
459 SpeechdStatus.max_fd--;
460
461 MSG(4, "Connection closed");
462
463 client_count--;
464 check_client_count();
465
466 return 0;
467 }
468
speechd_client_terminate(gpointer key,gpointer value,gpointer user)469 static gboolean speechd_client_terminate(gpointer key, gpointer value, gpointer user)
470 {
471 TFDSetElement *set;
472
473 set = (TFDSetElement *) value;
474 if (set == NULL) {
475 MSG(2, "Error: Empty connection, internal error");
476 if (SPEECHD_DEBUG)
477 FATAL("Internal error");
478 return TRUE;
479 }
480
481 if (set->fd > 0) {
482 MSG(4, "Closing connection on fd %d\n", set->fd);
483 speechd_connection_destroy(set->fd);
484 }
485 mem_free_fdset(set);
486 g_free(set);
487 return TRUE;
488 }
489
490 /* --- OUTPUT MODULES MANAGING --- */
491
speechd_modules_terminate(gpointer data,gpointer user_data)492 void speechd_modules_terminate(gpointer data, gpointer user_data)
493 {
494 OutputModule *module;
495
496 module = (OutputModule *) data;
497 if (module == NULL) {
498 MSG(2, "Error: Empty module, internal error");
499 return;
500 }
501 unload_output_module(module);
502
503 return;
504 }
505
speechd_modules_reload(gpointer data,gpointer user_data)506 void speechd_modules_reload(gpointer data, gpointer user_data)
507 {
508 OutputModule *module;
509
510 module = (OutputModule *) data;
511 if (module == NULL) {
512 MSG(2, "Empty module, internal error");
513 return;
514 }
515
516 reload_output_module(module);
517
518 return;
519 }
520
speechd_module_debug(gpointer data,gpointer user_data)521 void speechd_module_debug(gpointer data, gpointer user_data)
522 {
523 OutputModule *module;
524
525 module = (OutputModule *) data;
526 if (module == NULL) {
527 MSG(2, "Empty module, internal error");
528 return;
529 }
530
531 output_module_debug(module);
532
533 return;
534 }
535
speechd_module_nodebug(gpointer data,gpointer user_data)536 void speechd_module_nodebug(gpointer data, gpointer user_data)
537 {
538 OutputModule *module;
539
540 module = (OutputModule *) data;
541 if (module == NULL) {
542 MSG(2, "Empty module, internal error");
543 return;
544 }
545
546 output_module_nodebug(module);
547
548 return;
549 }
550
speechd_reload_dead_modules(gpointer user_data)551 static gboolean speechd_reload_dead_modules(gpointer user_data)
552 {
553 /* Reload dead modules */
554 g_list_foreach(output_modules, speechd_modules_reload, NULL);
555
556 /* Make sure there aren't any more child processes left */
557 while (waitpid(-1, NULL, WNOHANG) > 0) ;
558 return TRUE;
559 }
560
speechd_modules_debug(void)561 void speechd_modules_debug(void)
562 {
563 /* Redirect output to debug for all modules */
564 g_list_foreach(output_modules, speechd_module_debug, NULL);
565
566 }
567
speechd_modules_nodebug(void)568 void speechd_modules_nodebug(void)
569 {
570 /* Redirect output to normal for all modules */
571 g_list_foreach(output_modules, speechd_module_nodebug, NULL);
572 }
573
574 /* --- SPEECHD START/EXIT FUNCTIONS --- */
575
speechd_options_init(void)576 void speechd_options_init(void)
577 {
578 SpeechdOptions.spawn = FALSE;
579 SpeechdOptions.log_level_set = 0;
580 SpeechdOptions.communication_method = NULL;
581 SpeechdOptions.socket_path = NULL;
582 SpeechdOptions.port_set = 0;
583 SpeechdOptions.localhost_access_only_set = 0;
584 SpeechdOptions.pid_file = NULL;
585 SpeechdOptions.conf_file = NULL;
586 SpeechdOptions.module_dir = MODULEBINDIR;
587 SpeechdOptions.runtime_speechd_dir = NULL;
588 SpeechdOptions.log_dir = NULL;
589 SpeechdOptions.log_dir_set = 0;
590 SpeechdOptions.debug = 0;
591 SpeechdOptions.debug_destination = NULL;
592 debug_logfile = NULL;
593 spd_mode = SPD_MODE_DAEMON;
594 }
595
speechd_init()596 void speechd_init()
597 {
598 int ret;
599
600 SpeechdStatus.max_uid = 0;
601 SpeechdStatus.max_gid = 0;
602
603 /* Initialize inter-thread comm pipe */
604 if (pipe(speaking_pipe)) {
605 MSG(1, "Speaking pipe creation failed (%s)", strerror(errno));
606 FATAL("Can't create pipe");
607 }
608
609 /* Initialize Speech Dispatcher priority queue */
610 MessageQueue = g_malloc0(sizeof(TSpeechDQueue));
611 if (MessageQueue == NULL)
612 FATAL("Couldn't allocate memory for MessageQueue.");
613
614 /* Initialize lists */
615 MessagePausedList = NULL;
616
617 /* Initialize hash tables */
618 fd_settings = g_hash_table_new_full(g_int_hash, g_int_equal,
619 (GDestroyNotify) g_free, NULL);
620 assert(fd_settings != NULL);
621
622 fd_uid = g_hash_table_new_full(g_int_hash, g_int_equal,
623 (GDestroyNotify) g_free,
624 (GDestroyNotify) g_free);
625 assert(fd_uid != NULL);
626
627 language_default_modules = g_hash_table_new(g_str_hash, g_str_equal);
628 assert(language_default_modules != NULL);
629
630 speechd_sockets_status_init();
631
632 pause_requested = 0;
633 resume_requested = 0;
634
635 /* Perform some functionality tests */
636 if (g_module_supported() == FALSE)
637 DIE("Loadable modules not supported by current platform.\n");
638
639 if (_POSIX_VERSION < 199506L)
640 DIE("This system doesn't support POSIX.1c threads\n");
641
642 /* Fill GlobalFDSet with default values */
643 GlobalFDSet.min_delay_progress = 2000;
644
645 /* Initialize list of different client specific settings entries */
646 client_specific_settings = NULL;
647
648 /* Initialize mutexes, semaphores and synchronization */
649 ret = pthread_mutex_init(&element_free_mutex, NULL);
650 if (ret != 0)
651 DIE("Mutex initialization failed");
652
653 ret = pthread_mutex_init(&output_layer_mutex, NULL);
654 if (ret != 0)
655 DIE("Mutex initialization failed");
656
657 ret = pthread_mutex_init(&socket_com_mutex, NULL);
658 if (ret != 0)
659 DIE("Mutex initialization failed");
660
661 if (SpeechdOptions.log_dir == NULL) {
662 SpeechdOptions.log_dir =
663 g_strdup_printf("%s/log/",
664 SpeechdOptions.runtime_speechd_dir);
665 mkdir(SpeechdOptions.log_dir, S_IRWXU);
666 if (!SpeechdOptions.debug_destination) {
667 SpeechdOptions.debug_destination =
668 g_strdup_printf("%s/log/debug",
669 SpeechdOptions.runtime_speechd_dir);
670 mkdir(SpeechdOptions.debug_destination, S_IRWXU);
671 }
672 }
673
674 /* Load configuration from the config file */
675 MSG(4, "Reading Speech Dispatcher configuration from %s",
676 SpeechdOptions.conf_file);
677 speechd_load_configuration(NULL);
678
679 logging_init();
680
681 /* Check for output modules */
682 if (g_list_length(output_modules) == 0) {
683 DIE("No speech output modules were loaded - aborting...");
684 } else {
685 MSG(3, "Speech Dispatcher started with %d output module%s",
686 g_list_length(output_modules),
687 g_list_length(output_modules) > 1 ? "s" : "");
688 }
689
690 last_p5_block = NULL;
691 }
692
speechd_load_configuration(gpointer user_data)693 static gboolean speechd_load_configuration(gpointer user_data)
694 {
695 configfile_t *configfile = NULL;
696 GList *detected_modules = NULL;
697
698 /* Clean previous configuration */
699 if (output_modules != NULL) {
700 g_list_foreach(output_modules, speechd_modules_terminate, NULL);
701 g_list_free(output_modules);
702 output_modules = NULL;
703 }
704
705 /* Make sure there aren't any more child processes left */
706 while (waitpid(-1, NULL, WNOHANG) > 0) ;
707
708 /* Load new configuration */
709 load_default_global_set_options();
710
711 spd_num_options = 0;
712 spd_options = load_config_options(&spd_num_options);
713
714 /* Add the LAST option */
715 spd_options = add_config_option(spd_options, &spd_num_options, "", 0,
716 NULL, NULL, 0);
717
718 configfile =
719 dotconf_create(SpeechdOptions.conf_file, spd_options, 0,
720 CASE_INSENSITIVE);
721 if (configfile) {
722 configfile->includepath = g_strdup(SpeechdOptions.conf_dir);
723 MSG(5, "Config file include path is: %s",
724 configfile->includepath);
725 if (dotconf_command_loop(configfile) == 0)
726 DIE("Error reading config file\n");
727 dotconf_cleanup(configfile);
728 MSG(2, "Configuration has been read from \"%s\"",
729 SpeechdOptions.conf_file);
730
731 /* We need to load modules here, since this is called both by speechd_init
732 * and to handle SIGHUP. */
733 if (module_number_of_requested_modules() < 1) {
734 detected_modules = detect_output_modules(SpeechdOptions.module_dir,
735 SpeechdOptions.conf_dir);
736 while (detected_modules != NULL) {
737 char **parameters = detected_modules->data;
738 module_add_load_request(parameters[0],
739 parameters[1],
740 parameters[2],
741 parameters[3]);
742 g_free(detected_modules->data);
743 detected_modules->data = NULL;
744 detected_modules =
745 g_list_delete_link(detected_modules,
746 detected_modules);
747 }
748 }
749
750 module_load_requested_modules();
751 } else {
752 MSG(1, "Can't open %s", SpeechdOptions.conf_file);
753 }
754
755 free_config_options(spd_options, &spd_num_options);
756
757 return TRUE;
758 }
759
speechd_quit(gpointer user_data)760 static gboolean speechd_quit(gpointer user_data)
761 {
762 g_main_loop_quit(main_loop);
763 return FALSE;
764 }
765
766 /* --- PID FILES --- */
767
create_pid_file()768 int create_pid_file()
769 {
770 FILE *pid_file;
771 int pid_fd;
772 struct flock lock;
773 int ret;
774
775 /* If the file exists, examine it's lock */
776 pid_file = fopen(SpeechdOptions.pid_file, "r");
777 if (pid_file != NULL) {
778 pid_fd = fileno(pid_file);
779
780 lock.l_type = F_WRLCK;
781 lock.l_whence = SEEK_SET;
782 lock.l_start = 1;
783 lock.l_len = 3;
784
785 /* If there is a lock, exit, otherwise remove the old file */
786 ret = fcntl(pid_fd, F_GETLK, &lock);
787 if (ret == -1) {
788 MSG(-1,
789 "Can't check lock status of an existing pid file.\n");
790 return -1;
791 }
792
793 fclose(pid_file);
794 if (lock.l_type != F_UNLCK) {
795 MSG(-1, "Speech Dispatcher already running.\n");
796 return -1;
797 }
798
799 unlink(SpeechdOptions.pid_file);
800 }
801
802 /* Create a new pid file and lock it */
803 pid_file = fopen(SpeechdOptions.pid_file, "w");
804 if (pid_file == NULL) {
805 MSG(-1, "Can't create pid file in %s, wrong permissions?\n",
806 SpeechdOptions.pid_file);
807 return -1;
808 }
809 fprintf(pid_file, "%d\n", getpid());
810 fflush(pid_file);
811
812 pid_fd = fileno(pid_file);
813 lock.l_type = F_WRLCK;
814 lock.l_whence = SEEK_SET;
815 lock.l_start = 1;
816 lock.l_len = 3;
817
818 ret = fcntl(pid_fd, F_SETLK, &lock);
819 if (ret == -1) {
820 MSG(-1, "Can't set lock on pid file.\n");
821 return -1;
822 }
823
824 return 0;
825 }
826
destroy_pid_file(void)827 void destroy_pid_file(void)
828 {
829 unlink(SpeechdOptions.pid_file);
830 }
831
logging_init(void)832 void logging_init(void)
833 {
834 char *file_name =
835 g_strdup_printf("%s/speech-dispatcher.log", SpeechdOptions.log_dir);
836 assert(file_name != NULL);
837 if (!strncmp(file_name, "stdout", 6)) {
838 logfile = stdout;
839 } else if (!strncmp(file_name, "stderr", 6)) {
840 logfile = stderr;
841 } else {
842 logfile = fopen(file_name, "a");
843 if (logfile == NULL) {
844 fprintf(stderr,
845 "Error: can't open logging file %s! Using stdout.\n",
846 file_name);
847 logfile = stdout;
848 } else {
849 MSG(3, "Speech Dispatcher Logging to file %s",
850 file_name);
851 }
852 }
853
854 if (!debug_logfile)
855 debug_logfile = stdout;
856
857 g_free(file_name);
858 return;
859 }
860
861 /* --- Sockets --- */
862
make_local_socket(const char * filename)863 int make_local_socket(const char *filename)
864 {
865 struct sockaddr_un name;
866 int sock;
867 size_t size;
868
869 /* Create the socket. */
870 sock = socket(AF_UNIX, SOCK_STREAM, 0);
871 if (sock < 0) {
872 FATAL("Can't create local socket");
873 }
874
875 /* Bind a name to the socket. */
876 name.sun_family = AF_UNIX;
877 strncpy(name.sun_path, filename, sizeof(name.sun_path));
878 name.sun_path[sizeof(name.sun_path) - 1] = '\0';
879 size = SUN_LEN(&name);
880
881 if (bind(sock, (struct sockaddr *)&name, size) < 0) {
882 FATAL("Can't bind local socket");
883 }
884
885 if (listen(sock, 50) == -1) {
886 MSG(2, "listen failed: ERRNO:%s", strerror(errno));
887 FATAL("listen() failed for local socket");
888 }
889
890 return sock;
891 }
892
make_inet_socket(const int port)893 int make_inet_socket(const int port)
894 {
895 struct sockaddr_in server_address;
896 int server_socket;
897
898 /* Create an inet socket */
899 server_socket = socket(AF_INET, SOCK_STREAM, 0);
900 if (server_socket < 0) {
901 FATAL("Can't create inet socket");
902 }
903
904 /* Set REUSEADDR flag */
905 const int flag = 1;
906 if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &flag,
907 sizeof(int)))
908 MSG(2, "Error: Setting socket option failed!");
909
910 server_address.sin_family = AF_INET;
911
912 /* Enable access only to localhost or for any address
913 based on LocalhostAccessOnly configuration option. */
914 if (SpeechdOptions.localhost_access_only)
915 server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
916 else
917 server_address.sin_addr.s_addr = htonl(INADDR_ANY);
918
919 server_address.sin_port = htons(port);
920
921 MSG(4, "Opening inet socket connection");
922 if (bind(server_socket, (struct sockaddr *)&server_address,
923 sizeof(server_address)) == -1) {
924 MSG(-1, "bind() failed: %s", strerror(errno));
925 FATAL("Couldn't open inet socket, try a few minutes later.");
926 }
927
928 if (listen(server_socket, 50) == -1) {
929 MSG(2, "ERRNO:%s", strerror(errno));
930 FATAL
931 ("listen() failed for inet socket, another Speech Dispatcher running?");
932 }
933
934 return server_socket;
935 }
936
server_process_incoming(gint fd,GIOCondition condition,gpointer data)937 gboolean server_process_incoming (gint fd,
938 GIOCondition condition,
939 gpointer data)
940 {
941 int ret;
942
943 ret = speechd_connection_new(fd);
944 if (ret != 0) {
945 MSG(2, "Error: Failed to add new client!");
946 if (SPEECHD_DEBUG) {
947 FATAL("Failed to add new client");
948 }
949 }
950
951 return TRUE;
952 }
953
client_process_incoming(gint fd,GIOCondition condition,gpointer data)954 gboolean client_process_incoming (gint fd,
955 GIOCondition condition,
956 gpointer data)
957 {
958 int ret;
959 int nread;
960
961 ioctl(fd, FIONREAD, &nread);
962
963 if (nread == 0) {
964 /* client has gone */
965 ret = speechd_connection_destroy(fd);
966 if (ret != 0) {
967 MSG(2, "Error: Failed to close the client!");
968 }
969 return FALSE;
970 }
971
972 /* client sends some commands or data */
973 if (serve(fd) == -1) {
974 MSG(2, "Error: Failed to serve client on fd %d!", fd);
975 }
976
977 return TRUE;
978 }
979
check_client_count(void)980 void check_client_count(void)
981 {
982 if (client_count <= 0
983 && SpeechdOptions.server_timeout > 0) {
984 MSG(4, "Currently no clients connected, enabling shutdown timer.");
985 server_timeout_source = g_timeout_add_seconds(
986 SpeechdOptions.server_timeout,
987 speechd_quit, NULL);
988 } else {
989 if (server_timeout_source >= 0) {
990 MSG(4, "Clients connected, disabling shutdown timer.");
991 g_source_remove(server_timeout_source);
992 server_timeout_source = -1;
993 }
994 }
995 }
996
997 /* --- MAIN --- */
998
main(int argc,char * argv[])999 int main(int argc, char *argv[])
1000 {
1001 int ret;
1002 /* Autospawn helper variables */
1003 char *spawn_communication_method = NULL;
1004 int spawn_port = 0;
1005 char *spawn_socket_path = NULL;
1006
1007 /* Strip all permisions for 'others' from the files created */
1008 umask(007);
1009
1010 /* Initialize logging */
1011 logfile = stdout;
1012 SpeechdOptions.log_level = 1;
1013 custom_logfile = NULL;
1014 custom_log_kind = NULL;
1015
1016 /* initialize i18n support */
1017 i18n_init();
1018
1019 speechd_options_init();
1020
1021 options_parse(argc, argv);
1022
1023 if (SpeechdOptions.spawn) {
1024 /* In case of --spawn, copy the host port and socket_path
1025 parameters into temporary spawn_ variables for later comparison
1026 with the config file and unset them */
1027 if (SpeechdOptions.communication_method_set) {
1028 spawn_communication_method =
1029 g_strdup(SpeechdOptions.communication_method);
1030 g_free(SpeechdOptions.communication_method);
1031 SpeechdOptions.communication_method_set = 0;
1032 }
1033 if (SpeechdOptions.port_set) {
1034 spawn_port = SpeechdOptions.port;
1035 SpeechdOptions.port_set = 0;
1036 }
1037 if (SpeechdOptions.socket_path_set) {
1038 spawn_socket_path =
1039 g_strdup(SpeechdOptions.socket_path);
1040 g_free(SpeechdOptions.socket_path);
1041 SpeechdOptions.socket_path_set = 0;
1042 }
1043 }
1044
1045 MSG(1, "Speech Dispatcher " VERSION " starting");
1046
1047 /* By default, search for configuration options in $XDG_CONFIG_HOME/speech-dispatcher
1048 and sockets and pid files in $XDG_RUNTIME_DIR/speech-dispatcher */
1049 {
1050 const char *user_runtime_dir;
1051 const char *user_config_dir;
1052 char *test_speechd_conf_file = NULL;
1053
1054 user_runtime_dir = g_get_user_runtime_dir();
1055 user_config_dir = g_get_user_config_dir();
1056
1057 /* Setup a speechd-dispatcher directory or create a new one */
1058 SpeechdOptions.runtime_speechd_dir =
1059 g_strdup_printf("%s/speech-dispatcher", user_runtime_dir);
1060 MSG(4, "Trying to find %s", SpeechdOptions.runtime_speechd_dir);
1061 g_mkdir_with_parents(SpeechdOptions.runtime_speechd_dir,
1062 S_IRWXU);
1063 MSG(4, "Using directory: %s for pidfile and logging",
1064 SpeechdOptions.runtime_speechd_dir);
1065 /* Pidfile */
1066 if (SpeechdOptions.pid_file == NULL) {
1067 /* If no pidfile path specified on command line, use default local dir */
1068 SpeechdOptions.pid_file =
1069 g_strdup_printf("%s/pid/speech-dispatcher.pid",
1070 SpeechdOptions.runtime_speechd_dir);
1071 g_mkdir(g_path_get_dirname(SpeechdOptions.pid_file),
1072 S_IRWXU);
1073 }
1074 /* Config file */
1075 if (SpeechdOptions.conf_dir == NULL) {
1076 /* If no conf_dir was specified on command line, try default local config dir */
1077 SpeechdOptions.conf_dir =
1078 g_build_filename(user_config_dir,
1079 "speech-dispatcher", NULL);
1080 test_speechd_conf_file =
1081 g_build_filename(SpeechdOptions.conf_dir,
1082 "speechd.conf", NULL);
1083 if (!g_file_test
1084 (test_speechd_conf_file, G_FILE_TEST_IS_REGULAR)) {
1085 /* If the local configuration file doesn't exist, read the global configuration */
1086 if (strcmp(SYS_CONF, ""))
1087 SpeechdOptions.conf_dir =
1088 g_strdup(SYS_CONF);
1089 else
1090 SpeechdOptions.conf_dir =
1091 g_strdup("/etc/speech-dispatcher/");
1092 }
1093 g_free(test_speechd_conf_file);
1094 }
1095 SpeechdOptions.conf_file =
1096 g_strdup_printf("%s/speechd.conf", SpeechdOptions.conf_dir);
1097 }
1098
1099 /* Check for PID file or create a new one or exit if Speech Dispatcher
1100 is already running */
1101 if (create_pid_file() != 0)
1102 exit(1);
1103
1104 /* Handle --spawn request */
1105 if (SpeechdOptions.spawn) {
1106 /* Check whether spawning is not disabled */
1107 gchar *config_contents;
1108 int err;
1109 GRegex *regexp;
1110 int result;
1111
1112 err =
1113 g_file_get_contents(SpeechdOptions.conf_file,
1114 &config_contents, NULL, NULL);
1115 if (err == FALSE) {
1116 MSG(-1, "Error opening %s", SpeechdOptions.conf_file);
1117 FATAL("Can't open conf file");
1118 }
1119 regexp =
1120 g_regex_new("^[ ]*DisableAutoSpawn", G_REGEX_MULTILINE, 0,
1121 NULL);
1122 result = g_regex_match(regexp, config_contents, 0, NULL);
1123 if (result) {
1124 MSG(-1,
1125 "Autospawn requested but disabled in configuration");
1126 exit(1);
1127 }
1128 g_free(config_contents);
1129 g_regex_unref(regexp);
1130 MSG(2, "Starting Speech Dispatcher due to auto-spawn");
1131 }
1132
1133 /* Initialize logging mutex to workaround ctime threading bug */
1134 /* Must be done no later than here */
1135 ret = pthread_mutex_init(&logging_mutex, NULL);
1136 if (ret != 0) {
1137 fprintf(stderr, "Mutex initialization failed");
1138 exit(1);
1139 }
1140
1141 speechd_init();
1142
1143 /* Handle socket_path 'default' */
1144 // TODO: This is a hack, we should do that at appropriate places...
1145 if (!strcmp(SpeechdOptions.socket_path, "default")) {
1146 /* This code cannot be moved above next to conf_dir and pidpath resolution because
1147 we need to also consider the DotConf configuration, which is read in speechd_init() */
1148 GString *socket_filename;
1149 socket_filename = g_string_new("");
1150 if (SpeechdOptions.runtime_speechd_dir) {
1151 g_string_printf(socket_filename, "%s/speechd.sock",
1152 SpeechdOptions.runtime_speechd_dir);
1153 } else {
1154 FATAL
1155 ("Socket name file not set and user has no runtime directory");
1156 }
1157 g_free(SpeechdOptions.socket_path);
1158 SpeechdOptions.socket_path = g_strdup(socket_filename->str);
1159 g_string_free(socket_filename, 1);
1160 }
1161
1162 /* Check if the communication method corresponds to the spawn request */
1163 /* TODO: This should preferably be done much sooner, but the current
1164 configuration mechanism doesn't allow it */
1165 if (SpeechdOptions.spawn) {
1166 if (spawn_communication_method) {
1167 if (strcmp
1168 (spawn_communication_method,
1169 SpeechdOptions.communication_method)) {
1170 MSG(-1,
1171 "Autospawn failed: Mismatch in communication methods. Client "
1172 "requests %s, most probably due to its configuration or the value of "
1173 "the SPEECHD_ADDRESS environment variable, but the server is configured "
1174 "to provide the %s method.",
1175 spawn_communication_method,
1176 SpeechdOptions.communication_method);
1177 exit(1);
1178 } else {
1179 if (!strcmp
1180 (SpeechdOptions.communication_method,
1181 "inet_socket")) {
1182 /* Check port */
1183 if (spawn_port != 0)
1184 if (spawn_port !=
1185 SpeechdOptions.port) {
1186 MSG(-1,
1187 "Autospawn failed: Mismatch in port numbers. Server "
1188 "is configured to use the inet_socket method on the port %d "
1189 "while the client requests port %d, most probably due to its "
1190 "configuration or the value of the SPEECHD_ADDRESS environment "
1191 "variable.",
1192 SpeechdOptions.port,
1193 spawn_port);
1194 exit(1);
1195 }
1196 } else if (!strcmp
1197 (SpeechdOptions.communication_method,
1198 "unix_socket")) {
1199 /* Check socket name */
1200 if (spawn_socket_path)
1201 if (strcmp
1202 (spawn_socket_path,
1203 SpeechdOptions.socket_path))
1204 {
1205 MSG(-1,
1206 "Autospawn failed: Mismatch in socket names. The server "
1207 "is configured to provide a socket interface in %s, but the "
1208 "client requests a different path: %s. This is most probably "
1209 "due to the client application configuration or the value of "
1210 "the SPEECHD_ADDRESS environment variable.",
1211 SpeechdOptions.socket_path,
1212 spawn_socket_path);
1213 exit(1);
1214 }
1215 } else
1216 assert(0);
1217 }
1218 }
1219 g_free(spawn_communication_method);
1220 g_free(spawn_socket_path);
1221 }
1222
1223 if (!strcmp(SpeechdOptions.communication_method, "inet_socket")) {
1224 MSG(4, "Speech Dispatcher will use inet port %d",
1225 SpeechdOptions.port);
1226 /* Connect and start listening on inet socket */
1227 server_socket = make_inet_socket(SpeechdOptions.port);
1228 } else if (!strcmp(SpeechdOptions.communication_method, "unix_socket")) {
1229 /* Determine appropariate socket file name */
1230 MSG(4, "Speech Dispatcher will use local unix socket: %s",
1231 SpeechdOptions.socket_path);
1232 /* Delete an old socket file if it exists */
1233 if (g_file_test(SpeechdOptions.socket_path, G_FILE_TEST_EXISTS))
1234 if (g_unlink(SpeechdOptions.socket_path) == -1)
1235 FATAL
1236 ("Local socket file exists but impossible to delete. Wrong permissions?");
1237 /* Connect and start listening on local unix socket */
1238 server_socket = make_local_socket(SpeechdOptions.socket_path);
1239 } else {
1240 FATAL("Unknown communication method");
1241 }
1242
1243 /* Fork, set uid, chdir, etc. */
1244 if (spd_mode == SPD_MODE_DAEMON) {
1245 if (daemon(0, 0)) {
1246 FATAL("Can't fork child process");
1247 }
1248 /* Re-create the pid file under this process */
1249 unlink(SpeechdOptions.pid_file);
1250 if (create_pid_file() == -1)
1251 return -1;
1252 }
1253
1254 /* Set up the main loop and register signals */
1255 main_loop = g_main_loop_new(g_main_context_default(), FALSE);
1256 g_unix_signal_add(SIGINT, speechd_quit, NULL);
1257 g_unix_signal_add(SIGTERM, speechd_quit, NULL);
1258 g_unix_signal_add(SIGHUP, speechd_load_configuration, NULL);
1259 g_unix_signal_add(SIGUSR1, speechd_reload_dead_modules, NULL);
1260 (void)signal(SIGPIPE, SIG_IGN);
1261
1262 MSG(4, "Creating new thread for speak()");
1263 ret = pthread_create(&speak_thread, NULL, speak, NULL);
1264 if (ret != 0)
1265 FATAL("Speak thread failed!\n");
1266
1267 SpeechdStatus.max_fd = server_socket;
1268
1269 g_unix_fd_add(server_socket, G_IO_IN,
1270 server_process_incoming, NULL);
1271
1272 /* Now wait for clients and requests. */
1273 MSG(1, "Speech Dispatcher started and waiting for clients ...");
1274
1275 check_client_count();
1276
1277 g_main_loop_run(main_loop);
1278
1279 MSG(1, "Terminating...");
1280
1281 MSG(2, "Closing open connections...");
1282 /* We will browse through all the connections and close them. */
1283 g_hash_table_foreach_remove(fd_settings, speechd_client_terminate,
1284 NULL);
1285 g_hash_table_destroy(fd_settings);
1286
1287 MSG(4, "Closing speak() thread...");
1288 ret = pthread_cancel(speak_thread);
1289 if (ret != 0)
1290 FATAL("Speak thread failed to cancel!\n");
1291
1292 ret = pthread_join(speak_thread, NULL);
1293 if (ret != 0)
1294 FATAL("Speak thread failed to join!\n");
1295
1296 MSG(2, "Closing open output modules...");
1297 /* Call the close() function of each registered output module. */
1298 g_list_foreach(output_modules, speechd_modules_terminate, NULL);
1299 g_list_free(output_modules);
1300
1301 MSG(2, "Closing server connection...");
1302 if (close(server_socket) == -1)
1303 MSG(2, "close() failed: %s", strerror(errno));
1304
1305 MSG(4, "Removing pid file");
1306 destroy_pid_file();
1307
1308 fflush(NULL);
1309
1310 g_main_loop_unref(main_loop);
1311 main_loop = NULL;
1312
1313 MSG(2, "Speech Dispatcher terminated correctly");
1314
1315 exit(0);
1316 }
1317
check_locked(pthread_mutex_t * lock)1318 void check_locked(pthread_mutex_t * lock)
1319 {
1320 if (pthread_mutex_trylock(lock) == 0) {
1321 MSG(1,
1322 "CRITICAL ERROR: Not locked but accessing structure data!");
1323 fprintf(stderr, "WARNING! WARNING! MUTEX CHECK FAILED!\n");
1324 fflush(stderr);
1325 exit(0);
1326 }
1327 }
1328