Merged revisions 44378 via svnmerge from
[asterisk/asterisk.git] / res / res_smdi.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Copyright (C) 2005-2006, Digium, Inc.
5  *
6  * Matthew A. Nicholson <mnicholson@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file
21  * \brief SMDI support for Asterisk.
22  * \author Matthew A. Nicholson <mnicholson@digium.com>
23  */
24
25 #include "asterisk.h"
26
27 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <termios.h>
33 #include <sys/time.h>
34 #include <time.h>
35 #include <ctype.h>
36
37 #include "asterisk/module.h"
38 #include "asterisk/lock.h"
39 #include "asterisk/utils.h"
40 #include "asterisk/smdi.h"
41 #include "asterisk/config.h"
42 #include "asterisk/astobj.h"
43 #include "asterisk/io.h"
44 #include "asterisk/logger.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/options.h"
47
48 /* Message expiry time in milliseconds */
49 #define SMDI_MSG_EXPIRY_TIME    30000 /* 30 seconds */
50
51 static const char config_file[] = "smdi.conf";
52
53 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg);
54 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg);
55
56 static void *smdi_read(void *iface_p);
57 static int smdi_load(int reload);
58
59 struct module_symbols *me;      /* initialized in load_module() */
60
61 /*! \brief SMDI interface container. */
62 struct ast_smdi_interface_container {
63         ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
64 } smdi_ifaces;
65
66 /*! 
67  * \internal
68  * \brief Push an SMDI message to the back of an interface's message queue.
69  * \param iface a pointer to the interface to use.
70  * \param md_msg a pointer to the message to use.
71  */
72 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
73 {
74         ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
75 }
76
77 /*!
78  * \internal
79  * \brief Push an SMDI message to the back of an interface's message queue.
80  * \param iface a pointer to the interface to use.
81  * \param mwi_msg a pointer to the message to use.
82  */
83 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
84 {
85         ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
86 }
87
88 /*!
89  * \brief Set the MWI indicator for a mailbox.
90  * \param iface the interface to use.
91  * \param mailbox the mailbox to use.
92  */
93 int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
94 {
95         FILE *file;
96         int i;
97         
98         file = fopen(iface->name, "w");
99         if(!file) {
100                 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
101                 return 1;
102         }       
103
104         ASTOBJ_WRLOCK(iface);
105
106         fprintf(file, "OP:MWI ");
107
108         for(i = 0; i < iface->msdstrip; i++)
109            fprintf(file, "0");
110
111         fprintf(file, "%s!\x04", mailbox);
112         fclose(file);
113
114         ASTOBJ_UNLOCK(iface);
115         if (option_debug)
116                 ast_log(LOG_DEBUG, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
117         return 0;
118 }
119
120 /*! 
121  * \brief Unset the MWI indicator for a mailbox.
122  * \param iface the interface to use.
123  * \param mailbox the mailbox to use.
124  */
125 int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
126 {
127         FILE *file;
128         int i;
129         
130         file = fopen(iface->name, "w");
131         if(!file) {
132                 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
133                 return 1;
134         }       
135
136         ASTOBJ_WRLOCK(iface);
137
138         fprintf(file, "RMV:MWI ");
139
140         for(i = 0; i < iface->msdstrip; i++)
141            fprintf(file, "0");
142
143         fprintf(file, "%s!\x04", mailbox);
144         fclose(file);
145
146         ASTOBJ_UNLOCK(iface);
147         if (option_debug)
148                 ast_log(LOG_DEBUG, "Sent MWI unset message for %s on %s\n", mailbox, iface->name);
149         return 0;
150 }
151
152 /*!
153  * \brief Put an SMDI message back in the front of the queue.
154  * \param iface a pointer to the interface to use.
155  * \param md_msg a pointer to the message to use.
156  *
157  * This function puts a message back in the front of the specified queue.  It
158  * should be used if a message was popped but is not going to be processed for
159  * some reason, and the message needs to be returned to the queue.
160  */
161 void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
162 {
163         ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
164 }
165
166 /*!
167  * \brief Put an SMDI message back in the front of the queue.
168  * \param iface a pointer to the interface to use.
169  * \param mwi_msg a pointer to the message to use.
170  *
171  * This function puts a message back in the front of the specified queue.  It
172  * should be used if a message was popped but is not going to be processed for
173  * some reason, and the message needs to be returned to the queue.
174  */
175 void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
176 {
177         ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
178 }
179
180 /*! 
181  * \brief Get the next SMDI message from the queue.
182  * \param iface a pointer to the interface to use.
183  *
184  * This function pulls the first unexpired message from the SMDI message queue
185  * on the specified interface.  It will purge all expired SMDI messages before
186  * returning.
187  *
188  * \return the next SMDI message, or NULL if there were no pending messages.
189  */
190 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
191 {
192         struct ast_smdi_md_message *md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
193         struct timeval now;
194         long elapsed = 0;
195
196         /* purge old messages */
197         now = ast_tvnow();
198         while (md_msg) {
199                 elapsed = ast_tvdiff_ms(now, md_msg->timestamp);
200
201                 if (elapsed > iface->msg_expiry) {
202                         /* found an expired message */
203                         ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
204                         ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MD message queue.  Message was %ld milliseconds too old.\n",
205                                 iface->name, elapsed - iface->msg_expiry);
206                         md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
207                 }
208                 else {
209                         /* good message, return it */
210                         break;
211                 }
212         }
213
214         return md_msg;
215 }
216
217 /*!
218  * \brief Get the next SMDI message from the queue.
219  * \param iface a pointer to the interface to use.
220  * \param timeout the time to wait before returning in milliseconds.
221  *
222  * This function pulls a message from the SMDI message queue on the specified
223  * interface.  If no message is available this function will wait the specified
224  * amount of time before returning.
225  *
226  * \return the next SMDI message, or NULL if there were no pending messages and
227  * the timeout has expired.
228  */
229 extern struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
230 {
231         struct timeval start;
232         long diff = 0;
233         struct ast_smdi_md_message *msg;
234
235         start = ast_tvnow();
236         while (diff < timeout) {
237
238                 if ((msg = ast_smdi_md_message_pop(iface)))
239                         return msg;
240
241                 /* check timeout */
242                 diff = ast_tvdiff_ms(ast_tvnow(), start);
243         }
244
245         return (ast_smdi_md_message_pop(iface));
246 }
247
248 /*!
249  * \brief Get the next SMDI message from the queue.
250  * \param iface a pointer to the interface to use.
251  *
252  * This function pulls the first unexpired message from the SMDI message queue
253  * on the specified interface.  It will purge all expired SMDI messages before
254  * returning.
255  *
256  * \return the next SMDI message, or NULL if there were no pending messages.
257  */
258 extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
259 {
260         struct ast_smdi_mwi_message *mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
261         struct timeval now;
262         long elapsed = 0;
263
264         /* purge old messages */
265         now = ast_tvnow();
266         while (mwi_msg) {
267                 elapsed = ast_tvdiff_ms(now, mwi_msg->timestamp);
268
269                 if (elapsed > iface->msg_expiry) {
270                         /* found an expired message */
271                         ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
272                         ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MWI message queue.  Message was %ld milliseconds too old.\n",
273                                 iface->name, elapsed - iface->msg_expiry);
274                         mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
275                 }
276                 else {
277                         /* good message, return it */
278                         break;
279                 }
280         }
281
282         return mwi_msg;
283 }
284
285 /*!
286  * \brief Get the next SMDI message from the queue.
287  * \param iface a pointer to the interface to use.
288  * \param timeout the time to wait before returning in milliseconds.
289  *
290  * This function pulls a message from the SMDI message queue on the specified
291  * interface.  If no message is available this function will wait the specified
292  * amount of time before returning.
293  *
294  * \return the next SMDI message, or NULL if there were no pending messages and
295  * the timeout has expired.
296  */
297 extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
298 {
299         struct timeval start;
300         long diff = 0;
301         struct ast_smdi_mwi_message *msg;
302
303         start = ast_tvnow();
304         while (diff < timeout) {
305
306                 if ((msg = ast_smdi_mwi_message_pop(iface)))
307                         return msg;
308
309                 /* check timeout */
310                 diff = ast_tvdiff_ms(ast_tvnow(), start);
311         }
312
313         return (ast_smdi_mwi_message_pop(iface));
314 }
315
316 /*!
317  * \brief Find an SMDI interface with the specified name.
318  * \param iface_name the name/port of the interface to search for.
319  *
320  * \return a pointer to the interface located or NULL if none was found.  This
321  * actually returns an ASTOBJ reference and should be released using
322  * #ASTOBJ_UNREF(iface, ast_smdi_interface_destroy).
323  */
324 extern struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
325 {
326         return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
327 }
328
329 /*! \brief Read an SMDI message.
330  *
331  * \param iface_p the SMDI interface to read from.
332  *
333  * This function loops and reads from and SMDI interface.  It must be stopped
334  * using pthread_cancel().
335  */
336 static void *smdi_read(void *iface_p)
337 {
338         struct ast_smdi_interface *iface = iface_p;
339         struct ast_smdi_md_message *md_msg;
340         struct ast_smdi_mwi_message *mwi_msg;
341         char c = '\0';
342         char *cp = NULL;
343         int i;
344         int start = 0;
345                 
346         /* read an smdi message */
347         while ((c = fgetc(iface->file))) {
348
349                 /* check if this is the start of a message */
350                 if (!start) {
351                         if (c == 'M')
352                                 start = 1;
353                 }
354                 else { /* Determine if this is a MD or MWI message */
355                         if(c == 'D') { /* MD message */
356                                 start = 0;
357
358                                 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
359                                         ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
360                                         return NULL;
361                                 }
362                                 
363                                 ASTOBJ_INIT(md_msg);
364
365                                 /* read the message desk number */
366                                 for(i = 0; i < SMDI_MESG_DESK_NUM_LEN; i++)
367                                         md_msg->mesg_desk_num[i] = fgetc(iface->file);
368
369                                 md_msg->mesg_desk_num[SMDI_MESG_DESK_NUM_LEN] = '\0';
370
371                                 /* read the message desk terminal number */
372                                 for(i = 0; i < SMDI_MESG_DESK_TERM_LEN; i++)
373                                         md_msg->mesg_desk_term[i] = fgetc(iface->file);
374
375                                 md_msg->mesg_desk_term[SMDI_MESG_DESK_TERM_LEN] = '\0';
376
377                                 /* read the message type */
378                                 md_msg->type = fgetc(iface->file);
379                            
380                                 /* read the forwarding station number (may be blank) */
381                                 cp = &md_msg->fwd_st[0];
382                                 for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
383                                         if((c = fgetc(iface->file)) == ' ') {
384                                                 *cp = '\0';
385                                                 break;
386                                         }
387
388                                         /* store c in md_msg->fwd_st */
389                                         if( i >= iface->msdstrip)
390                                                 *cp++ = c;
391                                 }
392
393                                 /* make sure the value is null terminated, even if this truncates it */
394                                 md_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
395                                 cp = NULL;
396                                 
397                                 /* read the calling station number (may be blank) */
398                                 cp = &md_msg->calling_st[0];
399                                 for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
400                                         if (!isdigit((c = fgetc(iface->file)))) {
401                                                 *cp = '\0';
402                                                 break;
403                                         }
404
405                                         /* store c in md_msg->calling_st */
406                                         if (i >= iface->msdstrip)
407                                                 *cp++ = c;
408                                 }
409
410                                 /* make sure the value is null terminated, even if this truncates it */
411                                 md_msg->calling_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
412                                 cp = NULL;
413
414                                 /* add the message to the message queue */
415                                 md_msg->timestamp = ast_tvnow();
416                                 ast_smdi_md_message_push(iface, md_msg);
417                                 if (option_debug)
418                                         ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
419                                 
420                                 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
421
422                         } else if(c == 'W') { /* MWI message */
423                                 start = 0;
424
425                                 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
426                                         ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
427                                         return NULL;
428                                 }
429
430                                 ASTOBJ_INIT(mwi_msg);
431
432                                 /* discard the 'I' (from 'MWI') */
433                                 fgetc(iface->file);
434                                 
435                                 /* read the forwarding station number (may be blank) */
436                                 cp = &mwi_msg->fwd_st[0];
437                                 for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
438                                         if ((c = fgetc(iface->file)) == ' ') {
439                                                 *cp = '\0';
440                                                 break;
441                                         }
442
443                                         /* store c in md_msg->fwd_st */
444                                         if (i >= iface->msdstrip)
445                                                 *cp++ = c;
446                                 }
447
448                                 /* make sure the station number is null terminated, even if this will truncate it */
449                                 mwi_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
450                                 cp = NULL;
451                                 
452                                 /* read the mwi failure cause */
453                                 for (i = 0; i < SMDI_MWI_FAIL_CAUSE_LEN; i++)
454                                         mwi_msg->cause[i] = fgetc(iface->file);
455
456                                 mwi_msg->cause[SMDI_MWI_FAIL_CAUSE_LEN] = '\0';
457
458                                 /* add the message to the message queue */
459                                 mwi_msg->timestamp = ast_tvnow();
460                                 ast_smdi_mwi_message_push(iface, mwi_msg);
461                                 if (option_debug)
462                                         ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
463                                 
464                                 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
465                         } else {
466                                 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
467                                 start = 0;
468                         }
469                 }
470         }
471
472         ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
473         ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
474         return NULL;
475 }
476
477 /*! \brief ast_smdi_md_message destructor. */
478 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
479 {
480         free(msg);
481 }
482
483 /*! \brief ast_smdi_mwi_message destructor. */
484 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
485 {
486         free(msg);
487 }
488
489 /*! \brief ast_smdi_interface destructor. */
490 void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
491 {
492         if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
493                 pthread_cancel(iface->thread);
494                 pthread_join(iface->thread, NULL);
495         }
496         
497         iface->thread = AST_PTHREADT_STOP;
498         
499         if(iface->file) 
500                 fclose(iface->file);
501         
502         ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
503         ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
504         ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
505         ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
506         free(iface);
507
508         ast_module_unref(ast_module_info->self);
509 }
510
511 /*!
512  * \internal
513  * \brief Load and reload SMDI configuration.
514  * \param reload this should be 1 if we are reloading and 0 if not.
515  *
516  * This function loads/reloads the SMDI configuration and starts and stops
517  * interfaces accordingly.
518  *
519  * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
520  */
521 static int smdi_load(int reload)
522 {
523         struct ast_config *conf;
524         struct ast_variable *v;
525         struct ast_smdi_interface *iface = NULL;
526         int res = 0;
527
528         /* Config options */
529         speed_t baud_rate = B9600;     /* 9600 baud rate */
530         tcflag_t paritybit = PARENB;   /* even parity checking */
531         tcflag_t charsize = CS7;       /* seven bit characters */
532         int stopbits = 0;              /* One stop bit */
533         
534         int msdstrip = 0;              /* strip zero digits */
535         long msg_expiry = SMDI_MSG_EXPIRY_TIME;
536         
537         conf = ast_config_load(config_file);
538
539         if (!conf) {
540                 if (reload)
541                         ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
542                 else
543                         ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
544                 return 1;
545         }
546
547         /* Mark all interfaces that we are listening on.  We will unmark them
548          * as we find them in the config file, this way we know any interfaces
549          * still marked after we have finished parsing the config file should
550          * be stopped.
551          */
552         if (reload)
553                 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
554
555         for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
556                 if (!strcasecmp(v->name, "baudrate")) {
557                         if (!strcasecmp(v->value, "9600"))
558                                 baud_rate = B9600;
559                         else if(!strcasecmp(v->value, "4800"))
560                                 baud_rate = B4800;
561                         else if(!strcasecmp(v->value, "2400"))
562                                 baud_rate = B2400;
563                         else if(!strcasecmp(v->value, "1200"))
564                                 baud_rate = B1200;
565                         else {
566                                 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
567                                 baud_rate = B9600;
568                         }
569                 } else if (!strcasecmp(v->name, "msdstrip")) {
570                         if (!sscanf(v->value, "%d", &msdstrip)) {
571                                 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
572                                 msdstrip = 0;
573                         } else if (0 > msdstrip || msdstrip > 9) {
574                                 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
575                                 msdstrip = 0;
576                         }
577                 } else if (!strcasecmp(v->name, "msgexpirytime")) {
578                         if (!sscanf(v->value, "%ld", &msg_expiry)) {
579                                 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
580                                 msg_expiry = SMDI_MSG_EXPIRY_TIME;
581                         }
582                 } else if (!strcasecmp(v->name, "paritybit")) {
583                         if (!strcasecmp(v->value, "even"))
584                                 paritybit = PARENB;
585                         else if (!strcasecmp(v->value, "odd"))
586                                 paritybit = PARENB | PARODD;
587                         else if (!strcasecmp(v->value, "none"))
588                                 paritybit = ~PARENB;
589                         else {
590                                 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
591                                 paritybit = PARENB;
592                         }
593                 } else if (!strcasecmp(v->name, "charsize")) {
594                         if (!strcasecmp(v->value, "7"))
595                                 charsize = CS7;
596                         else if (!strcasecmp(v->value, "8"))
597                                 charsize = CS8;
598                         else {
599                                 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
600                                 charsize = CS7;
601                         }
602                 } else if (!strcasecmp(v->name, "twostopbits")) {
603                         stopbits = ast_true(v->name);
604                 } else if (!strcasecmp(v->name, "smdiport")) {
605                         if (reload) {
606                                 /* we are reloading, check if we are already
607                                  * monitoring this interface, if we are we do
608                                  * not want to start it again.  This also has
609                                  * the side effect of not updating different
610                                  * setting for the serial port, but it should
611                                  * be trivial to rewrite this section so that
612                                  * options on the port are changed without
613                                  * restarting the interface.  Or the interface
614                                  * could be restarted with out emptying the
615                                  * queue. */
616                                 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
617                                         ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
618                                         ASTOBJ_UNMARK(iface);
619                                         ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
620                                         continue;
621                                 }
622                         }
623                                                         
624                         if (!(iface = ast_calloc(1, sizeof(*iface))))
625                                 continue;
626
627                         ASTOBJ_INIT(iface);
628                         ASTOBJ_CONTAINER_INIT(&iface->md_q);
629                         ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
630
631                         ast_copy_string(iface->name, v->value, sizeof(iface->name));
632
633                         if (!(iface->file = fopen(iface->name, "r"))) {
634                                 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
635                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
636                                 continue;
637                         }
638
639                         iface->fd = fileno(iface->file);
640
641                         /* Set the proper attributes for our serial port. */
642
643                         /* get the current attributes from the port */
644                         if (tcgetattr(iface->fd, &iface->mode)) {
645                                 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
646                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
647                                 continue;
648                         }
649
650                         /* set the desired speed */
651                         if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
652                                 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
653                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
654                                 continue;
655                         }
656                         
657                         /* set the stop bits */
658                         if (stopbits)
659                            iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB;   /* set two stop bits */
660                         else
661                            iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB;  /* set one stop bit */
662
663                         /* set the parity */
664                         iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
665
666                         /* set the character size */
667                         iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
668                         
669                         /* commit the desired attributes */
670                         if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
671                                 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
672                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
673                                 continue;
674                         }
675
676                         /* set the msdstrip */
677                         iface->msdstrip = msdstrip;
678
679                         /* set the message expiry time */
680                         iface->msg_expiry = msg_expiry;
681
682                         /* start the listner thread */
683                         if (option_verbose > 2)
684                                 ast_verbose(VERBOSE_PREFIX_3 "Starting SMDI monitor thread for %s\n", iface->name);
685                         if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
686                                 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
687                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
688                                 continue;
689                         }
690
691                         ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
692                         ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
693                         ast_module_ref(ast_module_info->self);
694                 } else {
695                         ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
696                 }
697         }
698         ast_config_destroy(conf);
699
700         /* Prune any interfaces we should no longer monitor. */
701         if (reload)
702                 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
703         
704         ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
705         /* TODO: this is bad, we need an ASTOBJ method for this! */
706         if (!smdi_ifaces.head)
707                 res = 1;
708         ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
709                         
710         return res;
711 }
712
713 static int load_module(void)
714 {
715         int res;
716
717         /* initialize our containers */
718         memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
719         ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
720
721         /* load the config and start the listener threads*/
722         res = smdi_load(0);
723         if (res < 0) {
724                 return res;
725         } else if (res == 1) {
726                 ast_log(LOG_WARNING, "No SMDI interfaces are available to listen on, not starting SDMI listener.\n");
727                 return AST_MODULE_LOAD_DECLINE;;
728         } else
729                 return 0;
730 }
731
732 static int unload_module(void)
733 {
734         /* this destructor stops any running smdi_read threads */
735         ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
736         ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
737
738         return 0;
739 }
740
741 static int reload(void)
742 {
743         int res;
744
745         res = smdi_load(1);
746
747         if (res < 0) {
748                 return res;
749         } else if (res == 1) {
750                 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
751                 return 0;
752         } else
753                 return 0;
754 }
755
756 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
757                 .load = load_module,
758                 .unload = unload_module,
759                 .reload = reload,
760                );