diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index 9bcb8f1596..ec901ac037 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -2976,8 +2976,8 @@ static void run_externnotify(char *context, char *extension) else ast_smdi_mwi_unset(smdi_iface, extension); - if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) { - ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name); + if ((mwi_msg = ast_smdi_mwi_message_wait_station(smdi_iface, SMDI_MWI_WAIT_TIMEOUT, extension))) { + ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s\n", extension); if (!strncmp(mwi_msg->cause, "INV", 3)) ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st); else if (!strncmp(mwi_msg->cause, "BLK", 3)) @@ -2985,7 +2985,7 @@ static void run_externnotify(char *context, char *extension) ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause); ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy); } else { - ast_debug(1, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name); + ast_debug(1, "Successfully executed SMDI MWI change for %s\n", extension); } } @@ -8411,8 +8411,6 @@ static int load_config(int reload) } if (!smdi_iface) { ast_log(LOG_ERROR, "No valid SMDI interface specfied, disabling SMDI voicemail notification\n"); - } else { - ast_debug(1, "Using SMDI port %s\n", smdi_iface->name); } } diff --git a/channels/chan_zap.c b/channels/chan_zap.c index 36e0f934e0..f09dd621f7 100644 --- a/channels/chan_zap.c +++ b/channels/chan_zap.c @@ -2764,7 +2764,7 @@ static void destroy_zt_pvt(struct zt_pvt **pvt) if (p->next) p->next->prev = p->prev; if (p->use_smdi) - ASTOBJ_UNREF(p->smdi_iface, ast_smdi_interface_destroy); + ast_smdi_interface_unref(p->smdi_iface); if (p->mwi_event_sub) ast_event_unsubscribe(p->mwi_event_sub); if (p->vars) diff --git a/configs/smdi.conf.sample b/configs/smdi.conf.sample index 0325eba481..669530e814 100644 --- a/configs/smdi.conf.sample +++ b/configs/smdi.conf.sample @@ -41,3 +41,35 @@ ;msgexpirytime = 30000 ;smdiport => /dev/ttyS0 + + +[mailboxes] +; This section configures parameters related to MWI handling for the SMDI link. + +; This option configures the polling interval used to check to see if the +; mailboxes have any new messages. This option is specified in seconds. +; The default value is 10 seconds. +; +;pollinginterval=10 + +; Every other entry in this section of the configuration file is interpreted as +; a mapping between the mailbox ID on the SMDI link, and the local Asterisk +; mailbox name. In many cases, they are the same thing, but they still must be +; listed here so that this module knows which mailboxes it needs to pay +; attention to. +; +; Syntax: +; =[@Asterisk Voicemail Context] +; +; If no Asterisk voicemail context is specified, "default" will be assumed. +; +; Before specifying mailboxes, you must specify an SMDI interface. All mailbox +; definitions that follow will correspond to that SMDI interface. If you specify +; another interface, then all definitions following that will correspond to the +; new interface. +; +;smdiport=/dev/ttyS0 +;2565551234=1234@vmcontext1 +;2565555678=5678@vmcontext2 +;smdiport=/dev/ttyS1 +;2565559999=9999 diff --git a/include/asterisk/smdi.h b/include/asterisk/smdi.h index 6260d9d2d5..8f098abdd5 100644 --- a/include/asterisk/smdi.h +++ b/include/asterisk/smdi.h @@ -1,9 +1,10 @@ /* * Asterisk -- A telephony toolkit for Linux. * - * Copyright (C) 2005-2006, Digium, Inc. + * Copyright (C) 2005-2008, Digium, Inc. * * Matthew A. Nicholson + * Russell Bryant * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -20,6 +21,7 @@ * \file * \brief SMDI support for Asterisk. * \author Matthew A. Nicholson + * \author Russell Bryant */ @@ -73,16 +75,6 @@ struct ast_smdi_md_message { struct timeval timestamp; /* a timestamp for the message */ }; -/*! \brief SMDI message desk message queue. */ -struct ast_smdi_md_queue { - ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message); -}; - -/*! \brief SMDI message waiting indicator message queue. */ -struct ast_smdi_mwi_queue { - ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message); -}; - /*! * \brief SMDI interface structure. * @@ -90,38 +82,114 @@ struct ast_smdi_mwi_queue { * should be monitored for SMDI activity. The structure contains a message * queue of messages that have been received on the interface. */ -struct ast_smdi_interface { - ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1); - struct ast_smdi_md_queue md_q; - struct ast_smdi_mwi_queue mwi_q; - FILE *file; - int fd; - pthread_t thread; - struct termios mode; - int msdstrip; - long msg_expiry; -}; +struct ast_smdi_interface; +void ast_smdi_interface_unref(struct ast_smdi_interface *iface); -/* MD message queue functions */ +/*! + * \brief Get the next SMDI message from the queue. + * \param iface a pointer to the interface to use. + * + * This function pulls the first unexpired message from the SMDI message queue + * on the specified interface. It will purge all expired SMDI messages before + * returning. + * + * \return the next SMDI message, or NULL if there were no pending messages. + */ struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface); + +/*! + * \brief Get the next SMDI message from the queue. + * \param iface a pointer to the interface to use. + * \param timeout the time to wait before returning in milliseconds. + * + * This function pulls a message from the SMDI message queue on the specified + * interface. If no message is available this function will wait the specified + * amount of time before returning. + * + * \return the next SMDI message, or NULL if there were no pending messages and + * the timeout has expired. + */ struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout); + +/*! + * \brief Put an SMDI message back in the front of the queue. + * \param iface a pointer to the interface to use. + * \param md_msg a pointer to the message to use. + * + * This function puts a message back in the front of the specified queue. It + * should be used if a message was popped but is not going to be processed for + * some reason, and the message needs to be returned to the queue. + */ void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg); -/* MWI message queue functions */ +/*! + * \brief Get the next SMDI message from the queue. + * \param iface a pointer to the interface to use. + * + * This function pulls the first unexpired message from the SMDI message queue + * on the specified interface. It will purge all expired SMDI messages before + * returning. + * + * \return the next SMDI message, or NULL if there were no pending messages. + */ struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface); + +/*! + * \brief Get the next SMDI message from the queue. + * \param iface a pointer to the interface to use. + * \param timeout the time to wait before returning in milliseconds. + * + * This function pulls a message from the SMDI message queue on the specified + * interface. If no message is available this function will wait the specified + * amount of time before returning. + * + * \return the next SMDI message, or NULL if there were no pending messages and + * the timeout has expired. + */ struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout); +struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, + int timeout, const char *station); + +/*! + * \brief Put an SMDI message back in the front of the queue. + * \param iface a pointer to the interface to use. + * \param mwi_msg a pointer to the message to use. + * + * This function puts a message back in the front of the specified queue. It + * should be used if a message was popped but is not going to be processed for + * some reason, and the message needs to be returned to the queue. + */ void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg); +/*! + * \brief Find an SMDI interface with the specified name. + * \param iface_name the name/port of the interface to search for. + * + * \return a pointer to the interface located or NULL if none was found. This + * actually returns an ASTOBJ reference and should be released using + * #ASTOBJ_UNREF(iface, ast_smdi_interface_destroy). + */ struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name); -/* MWI functions */ +/*! + * \brief Set the MWI indicator for a mailbox. + * \param iface the interface to use. + * \param mailbox the mailbox to use. + */ int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox); + +/*! + * \brief Unset the MWI indicator for a mailbox. + * \param iface the interface to use. + * \param mailbox the mailbox to use. + */ int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox); +/*! \brief ast_smdi_md_message destructor. */ void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg); -void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg); -void ast_smdi_interface_destroy(struct ast_smdi_interface *iface); +/*! \brief ast_smdi_mwi_message destructor. */ +void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg); #endif /* !ASTERISK_SMDI_H */ diff --git a/res/res_smdi.c b/res/res_smdi.c index 1df720e83f..ec0b2c4f9d 100644 --- a/res/res_smdi.c +++ b/res/res_smdi.c @@ -1,9 +1,10 @@ /* * Asterisk -- A telephony toolkit for Linux. * - * Copyright (C) 2005-2006, Digium, Inc. + * Copyright (C) 2005-2008, Digium, Inc. * * Matthew A. Nicholson + * Russell Bryant * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -20,6 +21,10 @@ * \file * \brief SMDI support for Asterisk. * \author Matthew A. Nicholson + * \author Russell Bryant + * + * Here is a useful mailing list post that describes SMDI protocol details: + * \ref http://lists.digium.com/pipermail/asterisk-dev/2003-June/000884.html */ #include "asterisk.h" @@ -38,25 +43,120 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/config.h" #include "asterisk/astobj.h" #include "asterisk/io.h" +#include "asterisk/stringfields.h" +#include "asterisk/linkedlists.h" +#include "asterisk/app.h" +#include "asterisk/pbx.h" +#include "asterisk/channel.h" /* Message expiry time in milliseconds */ #define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */ static const char config_file[] = "smdi.conf"; -static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg); -static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg); - -static void *smdi_read(void *iface_p); -static int smdi_load(int reload); - -struct module_symbols *me; /* initialized in load_module() */ +/*! \brief SMDI message desk message queue. */ +struct ast_smdi_md_queue { + ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message); +}; + +/*! \brief SMDI message waiting indicator message queue. */ +struct ast_smdi_mwi_queue { + ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message); +}; + +struct ast_smdi_interface { + ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1); + struct ast_smdi_md_queue md_q; + ast_mutex_t md_q_lock; + ast_cond_t md_q_cond; + struct ast_smdi_mwi_queue mwi_q; + ast_mutex_t mwi_q_lock; + ast_cond_t mwi_q_cond; + FILE *file; + int fd; + pthread_t thread; + struct termios mode; + int msdstrip; + long msg_expiry; +}; /*! \brief SMDI interface container. */ struct ast_smdi_interface_container { ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface); } smdi_ifaces; +/*! \brief A mapping between an SMDI mailbox ID and an Asterisk mailbox */ +struct mailbox_mapping { + /*! This is the current state of the mailbox. It is simply on or + * off to indicate if there are messages waiting or not. */ + unsigned int cur_state:1; + /*! A Pointer to the appropriate SMDI interface */ + struct ast_smdi_interface *iface; + AST_DECLARE_STRING_FIELDS( + /*! The Name of the mailbox for the SMDI link. */ + AST_STRING_FIELD(smdi); + /*! The name of the mailbox on the Asterisk side */ + AST_STRING_FIELD(mailbox); + /*! The name of the voicemail context in use */ + AST_STRING_FIELD(context); + ); + AST_LIST_ENTRY(mailbox_mapping) entry; +}; + +/*! 10 seconds */ +#define DEFAULT_POLLING_INTERVAL 10 + +/*! \brief Data that gets used by the SMDI MWI monitoring thread */ +static struct { + /*! The thread ID */ + pthread_t thread; + ast_mutex_t lock; + ast_cond_t cond; + /*! A list of mailboxes that need to be monitored */ + AST_LIST_HEAD_NOLOCK(, mailbox_mapping) mailbox_mappings; + /*! Polling Interval for checking mailbox status */ + unsigned int polling_interval; + /*! Set to 1 to tell the polling thread to stop */ + unsigned int stop:1; + /*! The time that the last poll began */ + struct timeval last_poll; +} mwi_monitor = { + .thread = AST_PTHREADT_NULL, +}; + +static void ast_smdi_interface_destroy(struct ast_smdi_interface *iface) +{ + if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) { + pthread_cancel(iface->thread); + pthread_join(iface->thread, NULL); + } + + iface->thread = AST_PTHREADT_STOP; + + if (iface->file) + fclose(iface->file); + + ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy); + ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy); + ASTOBJ_CONTAINER_DESTROY(&iface->md_q); + ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q); + + ast_mutex_destroy(&iface->md_q_lock); + ast_cond_destroy(&iface->md_q_cond); + + ast_mutex_destroy(&iface->mwi_q_lock); + ast_cond_destroy(&iface->mwi_q_cond); + + free(iface); + + ast_module_unref(ast_module_info->self); +} + +void ast_smdi_interface_unref(struct ast_smdi_interface *iface) +{ + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); +} + /*! * \internal * \brief Push an SMDI message to the back of an interface's message queue. @@ -65,7 +165,10 @@ struct ast_smdi_interface_container { */ static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg) { + ast_mutex_lock(&iface->md_q_lock); ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg); + ast_cond_broadcast(&iface->md_q_cond); + ast_mutex_unlock(&iface->md_q_lock); } /*! @@ -76,15 +179,13 @@ static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct as */ static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg) { + ast_mutex_lock(&iface->mwi_q_lock); ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg); + ast_cond_broadcast(&iface->mwi_q_cond); + ast_mutex_unlock(&iface->mwi_q_lock); } -/*! - * \brief Set the MWI indicator for a mailbox. - * \param iface the interface to use. - * \param mailbox the mailbox to use. - */ -int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox) +static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on) { FILE *file; int i; @@ -96,224 +197,263 @@ int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox) ASTOBJ_WRLOCK(iface); - fprintf(file, "OP:MWI "); + fprintf(file, "%s:MWI ", on ? "OP" : "RMV"); for (i = 0; i < iface->msdstrip; i++) fprintf(file, "0"); fprintf(file, "%s!\x04", mailbox); + fclose(file); ASTOBJ_UNLOCK(iface); ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name); + return 0; } -/*! - * \brief Unset the MWI indicator for a mailbox. - * \param iface the interface to use. - * \param mailbox the mailbox to use. - */ -int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox) +int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox) { - FILE *file; - int i; - - if (!(file = fopen(iface->name, "w"))) { - ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno)); - return 1; - } - - ASTOBJ_WRLOCK(iface); - - fprintf(file, "RMV:MWI "); - - for (i = 0; i < iface->msdstrip; i++) - fprintf(file, "0"); - - fprintf(file, "%s!\x04", mailbox); - fclose(file); + return smdi_toggle_mwi(iface, mailbox, 1); +} - ASTOBJ_UNLOCK(iface); - ast_debug(1, "Sent MWI unset message for %s on %s\n", mailbox, iface->name); - return 0; +int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox) +{ + return smdi_toggle_mwi(iface, mailbox, 0); } -/*! - * \brief Put an SMDI message back in the front of the queue. - * \param iface a pointer to the interface to use. - * \param md_msg a pointer to the message to use. - * - * This function puts a message back in the front of the specified queue. It - * should be used if a message was popped but is not going to be processed for - * some reason, and the message needs to be returned to the queue. - */ void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg) { + ast_mutex_lock(&iface->md_q_lock); ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg); + ast_cond_broadcast(&iface->md_q_cond); + ast_mutex_unlock(&iface->md_q_lock); } -/*! - * \brief Put an SMDI message back in the front of the queue. - * \param iface a pointer to the interface to use. - * \param mwi_msg a pointer to the message to use. - * - * This function puts a message back in the front of the specified queue. It - * should be used if a message was popped but is not going to be processed for - * some reason, and the message needs to be returned to the queue. - */ void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg) { + ast_mutex_lock(&iface->mwi_q_lock); ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg); + ast_cond_broadcast(&iface->mwi_q_cond); + ast_mutex_unlock(&iface->mwi_q_lock); } -/*! - * \brief Get the next SMDI message from the queue. - * \param iface a pointer to the interface to use. - * - * This function pulls the first unexpired message from the SMDI message queue - * on the specified interface. It will purge all expired SMDI messages before - * returning. - * - * \return the next SMDI message, or NULL if there were no pending messages. - */ -struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface) -{ - struct ast_smdi_md_message *md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q); - struct timeval now; - long elapsed = 0; +enum smdi_message_type { + SMDI_MWI, + SMDI_MD, +}; - /* purge old messages */ - now = ast_tvnow(); - while (md_msg) { - elapsed = ast_tvdiff_ms(now, md_msg->timestamp); +static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type) +{ + switch (type) { + case SMDI_MWI: + return ast_mutex_lock(&iface->mwi_q_lock); + case SMDI_MD: + return ast_mutex_lock(&iface->md_q_lock); + } + + return -1; +} - if (elapsed > iface->msg_expiry) { - /* found an expired message */ - ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy); - ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MD message queue. Message was %ld milliseconds too old.\n", - iface->name, elapsed - iface->msg_expiry); - md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q); - } - else { - /* good message, return it */ - break; - } +static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type) +{ + switch (type) { + case SMDI_MWI: + return ast_mutex_unlock(&iface->mwi_q_lock); + case SMDI_MD: + return ast_mutex_unlock(&iface->md_q_lock); } - return md_msg; + return -1; } -/*! - * \brief Get the next SMDI message from the queue. - * \param iface a pointer to the interface to use. - * \param timeout the time to wait before returning in milliseconds. - * - * This function pulls a message from the SMDI message queue on the specified - * interface. If no message is available this function will wait the specified - * amount of time before returning. - * - * \return the next SMDI message, or NULL if there were no pending messages and - * the timeout has expired. - */ -extern struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout) +static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type) { - struct timeval start = ast_tvnow(); - long diff = 0; - struct ast_smdi_md_message *msg; + switch (type) { + case SMDI_MWI: + return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q); + case SMDI_MD: + return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q); + } + return NULL; +} - while (diff < timeout) { +static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type) +{ + struct ast_smdi_md_message *md_msg = msg; + struct ast_smdi_mwi_message *mwi_msg = msg; + + switch (type) { + case SMDI_MWI: + return mwi_msg->timestamp; + case SMDI_MD: + return md_msg->timestamp; + } - if ((msg = ast_smdi_md_message_pop(iface))) - return msg; + return ast_tv(0, 0); +} - /* check timeout */ - diff = ast_tvdiff_ms(ast_tvnow(), start); +static inline void unref_msg(void *msg, enum smdi_message_type type) +{ + struct ast_smdi_md_message *md_msg = msg; + struct ast_smdi_mwi_message *mwi_msg = msg; + + switch (type) { + case SMDI_MWI: + ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy); + case SMDI_MD: + ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy); } - - return (ast_smdi_md_message_pop(iface)); } -/*! - * \brief Get the next SMDI message from the queue. - * \param iface a pointer to the interface to use. - * - * This function pulls the first unexpired message from the SMDI message queue - * on the specified interface. It will purge all expired SMDI messages before - * returning. - * - * \return the next SMDI message, or NULL if there were no pending messages. - */ -extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface) +static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type) { - struct ast_smdi_mwi_message *mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q); struct timeval now = ast_tvnow(); long elapsed = 0; + void *msg; + + lock_msg_q(iface, type); + msg = unlink_from_msg_q(iface, type); + unlock_msg_q(iface, type); /* purge old messages */ - while (mwi_msg) { - elapsed = ast_tvdiff_ms(now, mwi_msg->timestamp); + while (msg) { + elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type)); if (elapsed > iface->msg_expiry) { /* found an expired message */ - ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy); - ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MWI message queue. Message was %ld milliseconds too old.\n", - iface->name, elapsed - iface->msg_expiry); - mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q); - } - else { - /* good message, return it */ + unref_msg(msg, type); + ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue. " + "Message was %ld milliseconds too old.\n", + iface->name, (type == SMDI_MD) ? "MD" : "MWI", + elapsed - iface->msg_expiry); + + lock_msg_q(iface, type); + msg = unlink_from_msg_q(iface, type); + unlock_msg_q(iface, type); + } else { + /* good message, put it back and return */ + switch (type) { + case SMDI_MD: + ast_smdi_md_message_push(iface, msg); + break; + case SMDI_MWI: + ast_smdi_mwi_message_push(iface, msg); + break; + } + unref_msg(msg, type); break; } } +} + +static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type) +{ + void *msg; + + purge_old_messages(iface, type); + + lock_msg_q(iface, type); + msg = unlink_from_msg_q(iface, type); + unlock_msg_q(iface, type); - return mwi_msg; + return msg; } -/*! - * \brief Get the next SMDI message from the queue. - * \param iface a pointer to the interface to use. - * \param timeout the time to wait before returning in milliseconds. - * - * This function pulls a message from the SMDI message queue on the specified - * interface. If no message is available this function will wait the specified - * amount of time before returning. - * - * \return the next SMDI message, or NULL if there were no pending messages and - * the timeout has expired. - */ -extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout) +static void *smdi_msg_find(struct ast_smdi_interface *iface, + enum smdi_message_type type, const char *station) { - struct timeval start = ast_tvnow(); + void *msg = NULL; + + purge_old_messages(iface, type); + + switch (type) { + case SMDI_MD: + msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, station); + break; + case SMDI_MWI: + msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, station); + break; + } + + return msg; +} + +static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout, + enum smdi_message_type type, const char *station) +{ + struct timeval start; long diff = 0; - struct ast_smdi_mwi_message *msg; + void *msg; while (diff < timeout) { + struct timespec ts = { 0, }; + struct timeval tv; + + lock_msg_q(iface, type); - if ((msg = ast_smdi_mwi_message_pop(iface))) + if ((msg = smdi_msg_find(iface, type, station))) { + unlock_msg_q(iface, type); return msg; + } + + tv = ast_tvadd(start, ast_tv(0, timeout)); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; + + /* If there were no messages in the queue, then go to sleep until one + * arrives. */ + + ast_cond_timedwait(&iface->md_q_cond, &iface->md_q_lock, &ts); + + if ((msg = smdi_msg_find(iface, type, station))) { + unlock_msg_q(iface, type); + return msg; + } + + unlock_msg_q(iface, type); /* check timeout */ diff = ast_tvdiff_ms(ast_tvnow(), start); } - return (ast_smdi_mwi_message_pop(iface)); + return NULL; +} + +struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface) +{ + return smdi_msg_pop(iface, SMDI_MD); } -/*! - * \brief Find an SMDI interface with the specified name. - * \param iface_name the name/port of the interface to search for. - * - * \return a pointer to the interface located or NULL if none was found. This - * actually returns an ASTOBJ reference and should be released using - * #ASTOBJ_UNREF(iface, ast_smdi_interface_destroy). - */ -extern struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name) +struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout) +{ + return smdi_message_wait(iface, timeout, SMDI_MD, NULL); +} + +struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface) +{ + return smdi_msg_pop(iface, SMDI_MWI); +} + +struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout) +{ + return smdi_message_wait(iface, timeout, SMDI_MWI, NULL); +} + +struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout, + const char *station) +{ + return smdi_message_wait(iface, timeout, SMDI_MWI, station); +} + +struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name) { return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name)); } -/*! \brief Read an SMDI message. +/*! + * \internal + * \brief Read an SMDI message. * * \param iface_p the SMDI interface to read from. * @@ -335,122 +475,166 @@ static void *smdi_read(void *iface_p) /* check if this is the start of a message */ if (!start) { - if (c == 'M') + if (c == 'M') { + ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n"); start = 1; + } + continue; } - else { /* Determine if this is a MD or MWI message */ - if(c == 'D') { /* MD message */ - start = 0; + + if (c == 'D') { /* MD message */ + start = 0; + + ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n"); + + if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) { + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + return NULL; + } + + ASTOBJ_INIT(md_msg); + + /* read the message desk number */ + for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) { + md_msg->mesg_desk_num[i] = fgetc(iface->file); + ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]); + } + + md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0'; + + ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num); - if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) { - ASTOBJ_UNREF(iface,ast_smdi_interface_destroy); - return NULL; + /* read the message desk terminal number */ + for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) { + md_msg->mesg_desk_term[i] = fgetc(iface->file); + ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]); + } + + md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0'; + + ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term); + + /* read the message type */ + md_msg->type = fgetc(iface->file); + + ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type); + + /* read the forwarding station number (may be blank) */ + cp = &md_msg->fwd_st[0]; + for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) { + if ((c = fgetc(iface->file)) == ' ') { + *cp = '\0'; + ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n"); + break; } - - ASTOBJ_INIT(md_msg); - - /* read the message desk number */ - for(i = 0; i < SMDI_MESG_DESK_NUM_LEN; i++) - md_msg->mesg_desk_num[i] = fgetc(iface->file); - - md_msg->mesg_desk_num[SMDI_MESG_DESK_NUM_LEN] = '\0'; - - /* read the message desk terminal number */ - for(i = 0; i < SMDI_MESG_DESK_TERM_LEN; i++) - md_msg->mesg_desk_term[i] = fgetc(iface->file); - - md_msg->mesg_desk_term[SMDI_MESG_DESK_TERM_LEN] = '\0'; - - /* read the message type */ - md_msg->type = fgetc(iface->file); - - /* read the forwarding station number (may be blank) */ - cp = &md_msg->fwd_st[0]; - for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) { - if((c = fgetc(iface->file)) == ' ') { - *cp = '\0'; - break; - } - /* store c in md_msg->fwd_st */ - if( i >= iface->msdstrip) - *cp++ = c; + /* store c in md_msg->fwd_st */ + if (i >= iface->msdstrip) { + ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c); + *cp++ = c; + } else { + ast_log(LOG_DEBUG, "Read a '%c', but didn't store it in the fwd station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip); } + } - /* make sure the value is null terminated, even if this truncates it */ - md_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0'; - cp = NULL; - - /* read the calling station number (may be blank) */ - cp = &md_msg->calling_st[0]; - for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) { - if (!isdigit((c = fgetc(iface->file)))) { - *cp = '\0'; - break; + /* make sure the value is null terminated, even if this truncates it */ + md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0'; + cp = NULL; + + ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st); + + /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look + * up a message on this field */ + ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name)); + + /* read the calling station number (may be blank) */ + cp = &md_msg->calling_st[0]; + for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) { + if (!isdigit((c = fgetc(iface->file)))) { + *cp = '\0'; + ast_log(LOG_DEBUG, "Read a '%c', but didn't store it in the calling station buffer because it's not a digit\n", c); + if (c == ' ') { + /* Don't break on a space. We may read the space before the calling station + * here if the forwarding station buffer filled up. */ + i--; /* We're still on the same character */ + continue; } + break; + } - /* store c in md_msg->calling_st */ - if (i >= iface->msdstrip) - *cp++ = c; + /* store c in md_msg->calling_st */ + if (i >= iface->msdstrip) { + ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c); + *cp++ = c; + } else { + ast_log(LOG_DEBUG, "Read a '%c', but didn't store it in the calling station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip); } + } + + /* make sure the value is null terminated, even if this truncates it */ + md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0'; + cp = NULL; + + ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st); - /* make sure the value is null terminated, even if this truncates it */ - md_msg->calling_st[SMDI_MAX_STATION_NUM_LEN] = '\0'; - cp = NULL; + /* add the message to the message queue */ + md_msg->timestamp = ast_tvnow(); + ast_smdi_md_message_push(iface, md_msg); + ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name); + + ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy); - /* add the message to the message queue */ - md_msg->timestamp = ast_tvnow(); - ast_smdi_md_message_push(iface, md_msg); - ast_debug(1, "Recieved SMDI MD message on %s\n", iface->name); - - ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy); + } else if (c == 'W') { /* MWI message */ + start = 0; - } else if(c == 'W') { /* MWI message */ - start = 0; + ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n"); - if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) { - ASTOBJ_UNREF(iface,ast_smdi_interface_destroy); - return NULL; - } + if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) { + ASTOBJ_UNREF(iface,ast_smdi_interface_destroy); + return NULL; + } - ASTOBJ_INIT(mwi_msg); - - /* discard the 'I' (from 'MWI') */ - fgetc(iface->file); - - /* read the forwarding station number (may be blank) */ - cp = &mwi_msg->fwd_st[0]; - for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) { - if ((c = fgetc(iface->file)) == ' ') { - *cp = '\0'; - break; - } + ASTOBJ_INIT(mwi_msg); - /* store c in md_msg->fwd_st */ - if (i >= iface->msdstrip) - *cp++ = c; + /* discard the 'I' (from 'MWI') */ + fgetc(iface->file); + + /* read the forwarding station number (may be blank) */ + cp = &mwi_msg->fwd_st[0]; + for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) { + if ((c = fgetc(iface->file)) == ' ') { + *cp = '\0'; + break; } - /* make sure the station number is null terminated, even if this will truncate it */ - mwi_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0'; - cp = NULL; - - /* read the mwi failure cause */ - for (i = 0; i < SMDI_MWI_FAIL_CAUSE_LEN; i++) - mwi_msg->cause[i] = fgetc(iface->file); - - mwi_msg->cause[SMDI_MWI_FAIL_CAUSE_LEN] = '\0'; - - /* add the message to the message queue */ - mwi_msg->timestamp = ast_tvnow(); - ast_smdi_mwi_message_push(iface, mwi_msg); - ast_debug(1, "Recieved SMDI MWI message on %s\n", iface->name); - - ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy); - } else { - ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c); - start = 0; + /* store c in md_msg->fwd_st */ + if (i >= iface->msdstrip) + *cp++ = c; } + + /* make sure the station number is null terminated, even if this will truncate it */ + mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0'; + cp = NULL; + + /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look + * up a message on this field */ + ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name)); + + /* read the mwi failure cause */ + for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++) + mwi_msg->cause[i] = fgetc(iface->file); + + mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0'; + + /* add the message to the message queue */ + mwi_msg->timestamp = ast_tvnow(); + ast_smdi_mwi_message_push(iface, mwi_msg); + ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name); + + ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy); + } else { + ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c); + start = 0; } } @@ -459,38 +643,130 @@ static void *smdi_read(void *iface_p) return NULL; } -/*! \brief ast_smdi_md_message destructor. */ void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg) { ast_free(msg); } -/*! \brief ast_smdi_mwi_message destructor. */ void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg) { ast_free(msg); } -/*! \brief ast_smdi_interface destructor. */ -void ast_smdi_interface_destroy(struct ast_smdi_interface *iface) +static void destroy_mailbox_mapping(struct mailbox_mapping *mm) { - if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) { - pthread_cancel(iface->thread); - pthread_join(iface->thread, NULL); - } - - iface->thread = AST_PTHREADT_STOP; - - if(iface->file) - fclose(iface->file); + ast_string_field_free_memory(mm); + ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy); + free(mm); +} + +static void destroy_all_mailbox_mappings(void) +{ + struct mailbox_mapping *mm; + + ast_mutex_lock(&mwi_monitor.lock); + while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry))) + destroy_mailbox_mapping(mm); + ast_mutex_unlock(&mwi_monitor.lock); +} + +static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface) +{ + struct mailbox_mapping *mm; + char *mailbox, *context; + + if (!(mm = ast_calloc(1, sizeof(*mm)))) + return; - ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy); - ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy); - ASTOBJ_CONTAINER_DESTROY(&iface->md_q); - ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q); - ast_free(iface); + if (ast_string_field_init(mm, 32)) { + free(mm); + return; + } - ast_module_unref(ast_module_info->self); + ast_string_field_set(mm, smdi, var->name); + + context = ast_strdupa(var->value); + mailbox = strsep(&context, "@"); + if (ast_strlen_zero(context)) + context = "default"; + + ast_string_field_set(mm, mailbox, mailbox); + ast_string_field_set(mm, context, context); + + mm->iface = ASTOBJ_REF(iface); + + ast_mutex_lock(&mwi_monitor.lock); + AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry); + ast_mutex_unlock(&mwi_monitor.lock); +} + +/*! + * \note Called with the mwi_monitor.lock locked + */ +static void poll_mailbox(struct mailbox_mapping *mm) +{ + char buf[1024]; + unsigned int state; + + snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context); + + state = !!ast_app_has_voicemail(mm->mailbox, NULL); + + if (state != mm->cur_state) { + if (state) + ast_smdi_mwi_set(mm->iface, mm->smdi); + else + ast_smdi_mwi_unset(mm->iface, mm->smdi); + + mm->cur_state = state; + } +} + +static void *mwi_monitor_handler(void *data) +{ + while (!mwi_monitor.stop) { + struct timespec ts = { 0, }; + struct timeval tv; + struct mailbox_mapping *mm; + + ast_mutex_lock(&mwi_monitor.lock); + + mwi_monitor.last_poll = ast_tvnow(); + + AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry) + poll_mailbox(mm); + + /* Sleep up to the configured polling interval. Allow unload_module() + * to signal us to wake up and exit. */ + tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0)); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; + ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts); + + ast_mutex_unlock(&mwi_monitor.lock); + } + + return NULL; +} + +static struct ast_smdi_interface *alloc_smdi_interface(void) +{ + struct ast_smdi_interface *iface; + + if (!(iface = ast_calloc(1, sizeof(*iface)))) + return NULL; + + ASTOBJ_INIT(iface); + ASTOBJ_CONTAINER_INIT(&iface->md_q); + ASTOBJ_CONTAINER_INIT(&iface->mwi_q); + + ast_mutex_init(&iface->md_q_lock); + ast_cond_init(&iface->md_q_cond, NULL); + + ast_mutex_init(&iface->mwi_q_lock); + ast_cond_init(&iface->mwi_q_cond, NULL); + + return iface; } /*! @@ -541,11 +817,11 @@ static int smdi_load(int reload) if (!strcasecmp(v->name, "baudrate")) { if (!strcasecmp(v->value, "9600")) baud_rate = B9600; - else if(!strcasecmp(v->value, "4800")) + else if (!strcasecmp(v->value, "4800")) baud_rate = B4800; - else if(!strcasecmp(v->value, "2400")) + else if (!strcasecmp(v->value, "2400")) baud_rate = B2400; - else if(!strcasecmp(v->value, "1200")) + else if (!strcasecmp(v->value, "1200")) baud_rate = B1200; else { ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno); @@ -605,14 +881,10 @@ static int smdi_load(int reload) continue; } } - - if (!(iface = ast_calloc(1, sizeof(*iface)))) + + if (!(iface = alloc_smdi_interface())) continue; - ASTOBJ_INIT(iface); - ASTOBJ_CONTAINER_INIT(&iface->md_q); - ASTOBJ_CONTAINER_INIT(&iface->mwi_q); - ast_copy_string(iface->name, v->value, sizeof(iface->name)); iface->thread = AST_PTHREADT_NULL; @@ -666,7 +938,7 @@ static int smdi_load(int reload) /* set the message expiry time */ iface->msg_expiry = msg_expiry; - /* start the listner thread */ + /* start the listener thread */ ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name); if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) { ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name); @@ -681,8 +953,46 @@ static int smdi_load(int reload) ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file); } } + + destroy_all_mailbox_mappings(); + mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL; + + iface = NULL; + + for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) { + if (!strcasecmp(v->name, "smdiport")) { + if (iface) + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + + if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) { + ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name); + continue; + } + } else if (!strcasecmp(v->name, "pollinginterval")) { + if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) { + ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value); + mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL; + } + } else { + if (!iface) { + ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n"); + continue; + } + append_mailbox_mapping(v, iface); + } + } + + if (iface) + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + ast_config_destroy(conf); + if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL + && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) { + ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n"); + return AST_MODULE_LOAD_FAILURE; + } + /* Prune any interfaces we should no longer monitor. */ if (reload) ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy); @@ -696,6 +1006,231 @@ static int smdi_load(int reload) return res; } +struct smdi_msg_datastore { + unsigned int id; + struct ast_smdi_interface *iface; + struct ast_smdi_md_message *md_msg; +}; + +static void smdi_msg_datastore_destroy(void *data) +{ + struct smdi_msg_datastore *smd = data; + + if (smd->iface) + ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy); + + if (smd->md_msg) + ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy); + + free(smd); +} + +static const struct ast_datastore_info smdi_msg_datastore_info = { + .type = "SMDIMSG", + .destroy = smdi_msg_datastore_destroy, +}; + +static int smdi_msg_id; + +/*! In milliseconds */ +#define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000 + +static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + struct ast_module_user *u; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(port); + AST_APP_ARG(station); + AST_APP_ARG(timeout); + ); + unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT; + int res = -1; + char *parse = NULL; + struct smdi_msg_datastore *smd = NULL; + struct ast_datastore *datastore = NULL; + struct ast_smdi_interface *iface = NULL; + struct ast_smdi_md_message *md_msg = NULL; + + u = ast_module_user_add(chan); + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n"); + goto return_error; + } + + if (!chan) { + ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n"); + goto return_error; + } + + ast_autoservice_start(chan); + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.port) || ast_strlen_zero(args.station)) { + ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n"); + goto return_error; + } + + if (!(iface = ast_smdi_interface_find(args.port))) { + ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port); + goto return_error; + } + + if (!ast_strlen_zero(args.timeout)) { + if (sscanf(args.timeout, "%u", &timeout) != 1) { + ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout); + timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT; + } + } + + if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.station))) { + ast_log(LOG_WARNING, "No SMDI message retrieved for station '%s' after " + "waiting %u ms.\n", args.station, timeout); + goto return_error; + } + + if (!(smd = ast_calloc(1, sizeof(*smd)))) + goto return_error; + + smd->iface = ASTOBJ_REF(iface); + smd->md_msg = ASTOBJ_REF(md_msg); + smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1); + snprintf(buf, len, "%u", smd->id); + + if (!(datastore = ast_channel_datastore_alloc(&smdi_msg_datastore_info, buf))) + goto return_error; + + datastore->data = smd; + + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); + + res = 0; + +return_error: + if (iface) + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + + if (md_msg) + ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy); + + if (smd && !datastore) + smdi_msg_datastore_destroy(smd); + + if (parse) + ast_autoservice_stop(chan); + + ast_module_user_remove(u); + + return res; +} + +static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + struct ast_module_user *u; + int res = -1; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(id); + AST_APP_ARG(component); + ); + char *parse; + struct ast_datastore *datastore = NULL; + struct smdi_msg_datastore *smd = NULL; + + u = ast_module_user_add(chan); + + if (!chan) { + ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n"); + goto return_error; + } + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n"); + goto return_error; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.id)) { + ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n"); + goto return_error; + } + + if (ast_strlen_zero(args.component)) { + ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n"); + goto return_error; + } + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id); + ast_channel_unlock(chan); + + if (!datastore) { + ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id); + goto return_error; + } + + smd = datastore->data; + + if (!strcasecmp(args.component, "station")) { + ast_copy_string(buf, smd->md_msg->fwd_st, len); + } else if (!strcasecmp(args.component, "callerid")) { + ast_copy_string(buf, smd->md_msg->calling_st, len); + } else if (!strcasecmp(args.component, "type")) { + snprintf(buf, len, "%c", smd->md_msg->type); + } else { + ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n", + args.component); + goto return_error; + } + + res = 0; + +return_error: + ast_module_user_remove(u); + + return 0; +} + +static struct ast_custom_function smdi_msg_retrieve_function = { + .name = "SMDI_MSG_RETRIEVE", + .synopsis = "Retrieve an SMDI message.", + .syntax = "SMDI_MSG_RETRIEVE(,[,timeout])", + .desc = + " This function is used to retrieve an incoming SMDI message. It returns\n" + "an ID which can be used with the SMDI_MSG() function to access details of\n" + "the message. Note that this is a destructive function in the sense that\n" + "once an SMDI message is retrieved using this function, it is no longer in\n" + "the global SMDI message queue, and can not be accessed by any other Asterisk\n" + "channels. The timeout for this function is optional, and the default is\n" + "3 seconds. When providing a timeout, it should be in milliseconds.\n" + "", + .read = smdi_msg_retrieve_read, +}; + +static struct ast_custom_function smdi_msg_function = { + .name = "SMDI_MSG", + .synopsis = "Retrieve details about an SMDI message.", + .syntax = "SMDI_MSG(,)", + .desc = + " This function is used to access details of an SMDI message that was\n" + "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n" + "function.\n" + " Valid message components are:\n" + " station - The forwarding station\n" + " callerid - The callerID of the calling party that was forwarded\n" + " type - The call type. The value here is the exact character\n" + " that came in on the SMDI link. Typically, example values\n" + " are: D - Direct Calls, A - Forward All Calls,\n" + " B - Forward Busy Calls, N - Forward No Answer Calls\n" + "", + .read = smdi_msg_read, +}; + static int load_module(void) { int res; @@ -704,6 +1239,12 @@ static int load_module(void) memset(&smdi_ifaces, 0, sizeof(smdi_ifaces)); ASTOBJ_CONTAINER_INIT(&smdi_ifaces); + ast_mutex_init(&mwi_monitor.lock); + ast_cond_init(&mwi_monitor.cond, NULL); + + ast_custom_function_register(&smdi_msg_retrieve_function); + ast_custom_function_register(&smdi_msg_function); + /* load the config and start the listener threads*/ res = smdi_load(0); if (res < 0) { @@ -711,8 +1252,9 @@ static int load_module(void) } else if (res == 1) { ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n"); return AST_MODULE_LOAD_DECLINE; - } else - return AST_MODULE_LOAD_SUCCESS; + } + + return AST_MODULE_LOAD_SUCCESS; } static int unload_module(void) @@ -721,6 +1263,18 @@ static int unload_module(void) ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy); ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces); + destroy_all_mailbox_mappings(); + + ast_mutex_lock(&mwi_monitor.lock); + mwi_monitor.stop = 1; + ast_cond_signal(&mwi_monitor.cond); + ast_mutex_unlock(&mwi_monitor.lock); + + pthread_join(mwi_monitor.thread, NULL); + + ast_custom_function_unregister(&smdi_msg_retrieve_function); + ast_custom_function_unregister(&smdi_msg_function); + return 0; }