diff options
Diffstat (limited to '1.4.23-rc4/apps/app_queue.c')
-rw-r--r-- | 1.4.23-rc4/apps/app_queue.c | 5140 |
1 files changed, 0 insertions, 5140 deletions
diff --git a/1.4.23-rc4/apps/app_queue.c b/1.4.23-rc4/apps/app_queue.c deleted file mode 100644 index 474ba9d88..000000000 --- a/1.4.23-rc4/apps/app_queue.c +++ /dev/null @@ -1,5140 +0,0 @@ -/* - * Asterisk -- An open source telephony toolkit. - * - * Copyright (C) 1999 - 2006, Digium, Inc. - * - * Mark Spencer <markster@digium.com> - * - * See http://www.asterisk.org for more information about - * the Asterisk project. Please do not directly contact - * any of the maintainers of this project for assistance; - * the project provides a web site, mailing lists and IRC - * channels for your use. - * - * This program is free software, distributed under the terms of - * the GNU General Public License Version 2. See the LICENSE file - * at the top of the source tree. - */ - -/*! \file - * - * \brief True call queues with optional send URL on answer - * - * \author Mark Spencer <markster@digium.com> - * - * \arg Config in \ref Config_qu queues.conf - * - * \par Development notes - * \note 2004-11-25: Persistent Dynamic Members added by: - * NetNation Communications (www.netnation.com) - * Kevin Lindsay <kevinl@netnation.com> - * - * Each dynamic agent in each queue is now stored in the astdb. - * When asterisk is restarted, each agent will be automatically - * readded into their recorded queues. This feature can be - * configured with the 'persistent_members=<1|0>' setting in the - * '[general]' category in queues.conf. The default is on. - * - * \note 2004-06-04: Priorities in queues added by inAccess Networks (work funded by Hellas On Line (HOL) www.hol.gr). - * - * \note These features added by David C. Troy <dave@toad.net>: - * - Per-queue holdtime calculation - * - Estimated holdtime announcement - * - Position announcement - * - Abandoned/completed call counters - * - Failout timer passed as optional app parameter - * - Optional monitoring of calls, started when call is answered - * - * Patch Version 1.07 2003-12-24 01 - * - * Added servicelevel statistic by Michiel Betel <michiel@betel.nl> - * Added Priority jumping code for adding and removing queue members by Jonathan Stanton <asterisk@doilooklikeicare.com> - * - * Fixed to work with CVS as of 2004-02-25 and released as 1.07a - * by Matthew Enger <m.enger@xi.com.au> - * - * \ingroup applications - */ - -/*** MODULEINFO - <depend>res_monitor</depend> - ***/ - -#include "asterisk.h" - -ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - -#include <stdlib.h> -#include <errno.h> -#include <unistd.h> -#include <string.h> -#include <stdlib.h> -#include <stdio.h> -#include <sys/time.h> -#include <sys/signal.h> -#include <netinet/in.h> - -#include "asterisk/lock.h" -#include "asterisk/file.h" -#include "asterisk/logger.h" -#include "asterisk/channel.h" -#include "asterisk/pbx.h" -#include "asterisk/options.h" -#include "asterisk/app.h" -#include "asterisk/linkedlists.h" -#include "asterisk/module.h" -#include "asterisk/translate.h" -#include "asterisk/say.h" -#include "asterisk/features.h" -#include "asterisk/musiconhold.h" -#include "asterisk/cli.h" -#include "asterisk/manager.h" -#include "asterisk/config.h" -#include "asterisk/monitor.h" -#include "asterisk/utils.h" -#include "asterisk/causes.h" -#include "asterisk/astdb.h" -#include "asterisk/devicestate.h" -#include "asterisk/stringfields.h" -#include "asterisk/astobj2.h" -#include "asterisk/global_datastores.h" - -/* Please read before modifying this file. - * There are three locks which are regularly used - * throughout this file, the queue list lock, the lock - * for each individual queue, and the interface list lock. - * Please be extra careful to always lock in the following order - * 1) queue list lock - * 2) individual queue lock - * 3) interface list lock - * This order has sort of "evolved" over the lifetime of this - * application, but it is now in place this way, so please adhere - * to this order! - */ - - -enum { - QUEUE_STRATEGY_RINGALL = 0, - QUEUE_STRATEGY_ROUNDROBIN, - QUEUE_STRATEGY_LEASTRECENT, - QUEUE_STRATEGY_FEWESTCALLS, - QUEUE_STRATEGY_RANDOM, - QUEUE_STRATEGY_RRMEMORY -}; - -static struct strategy { - int strategy; - char *name; -} strategies[] = { - { QUEUE_STRATEGY_RINGALL, "ringall" }, - { QUEUE_STRATEGY_ROUNDROBIN, "roundrobin" }, - { QUEUE_STRATEGY_LEASTRECENT, "leastrecent" }, - { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" }, - { QUEUE_STRATEGY_RANDOM, "random" }, - { QUEUE_STRATEGY_RRMEMORY, "rrmemory" }, -}; - -#define DEFAULT_RETRY 5 -#define DEFAULT_TIMEOUT 15 -#define RECHECK 1 /* Recheck every second to see we we're at the top yet */ -#define MAX_PERIODIC_ANNOUNCEMENTS 10 /* The maximum periodic announcements we can have */ - -#define RES_OKAY 0 /* Action completed */ -#define RES_EXISTS (-1) /* Entry already exists */ -#define RES_OUTOFMEMORY (-2) /* Out of memory */ -#define RES_NOSUCHQUEUE (-3) /* No such queue */ -#define RES_NOT_DYNAMIC (-4) /* Member is not dynamic */ - -static char *app = "Queue"; - -static char *synopsis = "Queue a call for a call queue"; - -static char *descrip = -" Queue(queuename[|options[|URL][|announceoverride][|timeout][|AGI]):\n" -"Queues an incoming call in a particular call queue as defined in queues.conf.\n" -"This application will return to the dialplan if the queue does not exist, or\n" -"any of the join options cause the caller to not enter the queue.\n" -"The option string may contain zero or more of the following characters:\n" -" 'd' -- data-quality (modem) call (minimum delay).\n" -" 'h' -- allow callee to hang up by hitting '*', or whatver disconnect sequence\n" -" defined in the featuremap section in features.conf.\n" -" 'H' -- allow caller to hang up by hitting '*', or whatever disconnect sequence\n" -" defined in the featuremap section in features.conf.\n" -" 'n' -- no retries on the timeout; will exit this application and \n" -" go to the next step.\n" -" 'i' -- ignore call forward requests from queue members and do nothing\n" -" when they are requested.\n" -" 'r' -- ring instead of playing MOH\n" -" 't' -- allow the called user transfer the calling user by pressing '#' or\n" -" whatever blindxfer sequence defined in the featuremap section in\n" -" features.conf\n" -" 'T' -- to allow the calling user to transfer the call by pressing '#' or\n" -" whatever blindxfer sequence defined in the featuremap section in\n" -" features.conf\n" -" 'w' -- allow the called user to write the conversation to disk via Monitor\n" -" by pressing the automon sequence defined in the featuremap section in\n" -" features.conf\n" -" 'W' -- allow the calling user to write the conversation to disk via Monitor\n" -" by pressing the automon sequence defined in the featuremap section in\n" -" features.conf\n" -" In addition to transferring the call, a call may be parked and then picked\n" -"up by another user, by transferring to the parking lot extension. See features.conf.\n" -" The optional URL will be sent to the called party if the channel supports\n" -"it.\n" -" The optional AGI parameter will setup an AGI script to be executed on the \n" -"calling party's channel once they are connected to a queue member.\n" -" The timeout will cause the queue to fail out after a specified number of\n" -"seconds, checked between each queues.conf 'timeout' and 'retry' cycle.\n" -" This application sets the following channel variable upon completion:\n" -" QUEUESTATUS The status of the call as a text string, one of\n" -" TIMEOUT | FULL | JOINEMPTY | LEAVEEMPTY | JOINUNAVAIL | LEAVEUNAVAIL\n"; - -static char *app_aqm = "AddQueueMember" ; -static char *app_aqm_synopsis = "Dynamically adds queue members" ; -static char *app_aqm_descrip = -" AddQueueMember(queuename[|interface[|penalty[|options[|membername]]]]):\n" -"Dynamically adds interface to an existing queue.\n" -"If the interface is already in the queue and there exists an n+101 priority\n" -"then it will then jump to this priority. Otherwise it will return an error\n" -"The option string may contain zero or more of the following characters:\n" -" 'j' -- jump to +101 priority when appropriate.\n" -" This application sets the following channel variable upon completion:\n" -" AQMSTATUS The status of the attempt to add a queue member as a \n" -" text string, one of\n" -" ADDED | MEMBERALREADY | NOSUCHQUEUE \n" -"Example: AddQueueMember(techsupport|SIP/3000)\n" -""; - -static char *app_rqm = "RemoveQueueMember" ; -static char *app_rqm_synopsis = "Dynamically removes queue members" ; -static char *app_rqm_descrip = -" RemoveQueueMember(queuename[|interface[|options]]):\n" -"Dynamically removes interface to an existing queue\n" -"If the interface is NOT in the queue and there exists an n+101 priority\n" -"then it will then jump to this priority. Otherwise it will return an error\n" -"The option string may contain zero or more of the following characters:\n" -" 'j' -- jump to +101 priority when appropriate.\n" -" This application sets the following channel variable upon completion:\n" -" RQMSTATUS The status of the attempt to remove a queue member as a\n" -" text string, one of\n" -" REMOVED | NOTINQUEUE | NOSUCHQUEUE \n" -"Example: RemoveQueueMember(techsupport|SIP/3000)\n" -""; - -static char *app_pqm = "PauseQueueMember" ; -static char *app_pqm_synopsis = "Pauses a queue member" ; -static char *app_pqm_descrip = -" PauseQueueMember([queuename]|interface[|options]):\n" -"Pauses (blocks calls for) a queue member.\n" -"The given interface will be paused in the given queue. This prevents\n" -"any calls from being sent from the queue to the interface until it is\n" -"unpaused with UnpauseQueueMember or the manager interface. If no\n" -"queuename is given, the interface is paused in every queue it is a\n" -"member of. If the interface is not in the named queue, or if no queue\n" -"is given and the interface is not in any queue, it will jump to\n" -"priority n+101, if it exists and the appropriate options are set.\n" -"The application will fail if the interface is not found and no extension\n" -"to jump to exists.\n" -"The option string may contain zero or more of the following characters:\n" -" 'j' -- jump to +101 priority when appropriate.\n" -" This application sets the following channel variable upon completion:\n" -" PQMSTATUS The status of the attempt to pause a queue member as a\n" -" text string, one of\n" -" PAUSED | NOTFOUND\n" -"Example: PauseQueueMember(|SIP/3000)\n"; - -static char *app_upqm = "UnpauseQueueMember" ; -static char *app_upqm_synopsis = "Unpauses a queue member" ; -static char *app_upqm_descrip = -" UnpauseQueueMember([queuename]|interface[|options]):\n" -"Unpauses (resumes calls to) a queue member.\n" -"This is the counterpart to PauseQueueMember and operates exactly the\n" -"same way, except it unpauses instead of pausing the given interface.\n" -"The option string may contain zero or more of the following characters:\n" -" 'j' -- jump to +101 priority when appropriate.\n" -" This application sets the following channel variable upon completion:\n" -" UPQMSTATUS The status of the attempt to unpause a queue \n" -" member as a text string, one of\n" -" UNPAUSED | NOTFOUND\n" -"Example: UnpauseQueueMember(|SIP/3000)\n"; - -static char *app_ql = "QueueLog" ; -static char *app_ql_synopsis = "Writes to the queue_log" ; -static char *app_ql_descrip = -" QueueLog(queuename|uniqueid|agent|event[|additionalinfo]):\n" -"Allows you to write your own events into the queue log\n" -"Example: QueueLog(101|${UNIQUEID}|${AGENT}|WENTONBREAK|600)\n"; - -/*! \brief Persistent Members astdb family */ -static const char *pm_family = "Queue/PersistentMembers"; -/* The maximum length of each persistent member queue database entry */ -#define PM_MAX_LEN 8192 - -/*! \brief queues.conf [general] option */ -static int queue_persistent_members = 0; - -/*! \brief queues.conf per-queue weight option */ -static int use_weight = 0; - -/*! \brief queues.conf [general] option */ -static int autofill_default = 0; - -/*! \brief queues.conf [general] option */ -static int montype_default = 0; - -enum queue_result { - QUEUE_UNKNOWN = 0, - QUEUE_TIMEOUT = 1, - QUEUE_JOINEMPTY = 2, - QUEUE_LEAVEEMPTY = 3, - QUEUE_JOINUNAVAIL = 4, - QUEUE_LEAVEUNAVAIL = 5, - QUEUE_FULL = 6, -}; - -const struct { - enum queue_result id; - char *text; -} queue_results[] = { - { QUEUE_UNKNOWN, "UNKNOWN" }, - { QUEUE_TIMEOUT, "TIMEOUT" }, - { QUEUE_JOINEMPTY,"JOINEMPTY" }, - { QUEUE_LEAVEEMPTY, "LEAVEEMPTY" }, - { QUEUE_JOINUNAVAIL, "JOINUNAVAIL" }, - { QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" }, - { QUEUE_FULL, "FULL" }, -}; - -/*! \brief We define a custom "local user" structure because we - use it not only for keeping track of what is in use but - also for keeping track of who we're dialing. - - There are two "links" defined in this structure, q_next and call_next. - q_next links ALL defined callattempt structures into a linked list. call_next is - a link which allows for a subset of the callattempts to be traversed. This subset - is used in wait_for_answer so that irrelevant callattempts are not traversed. This - also is helpful so that queue logs are always accurate in the case where a call to - a member times out, especially if using the ringall strategy. */ - -struct callattempt { - struct callattempt *q_next; - struct callattempt *call_next; - struct ast_channel *chan; - char interface[256]; - int stillgoing; - int metric; - int oldstatus; - time_t lastcall; - struct member *member; -}; - - -struct queue_ent { - struct call_queue *parent; /*!< What queue is our parent */ - char moh[80]; /*!< Name of musiconhold to be used */ - char announce[80]; /*!< Announcement to play for member when call is answered */ - char context[AST_MAX_CONTEXT]; /*!< Context when user exits queue */ - char digits[AST_MAX_EXTENSION]; /*!< Digits entered while in queue */ - int valid_digits; /*!< Digits entered correspond to valid extension. Exited */ - int pos; /*!< Where we are in the queue */ - int prio; /*!< Our priority */ - int last_pos_said; /*!< Last position we told the user */ - time_t last_periodic_announce_time; /*!< The last time we played a periodic announcement */ - int last_periodic_announce_sound; /*!< The last periodic announcement we made */ - time_t last_pos; /*!< Last time we told the user their position */ - int opos; /*!< Where we started in the queue */ - int handled; /*!< Whether our call was handled */ - int pending; /*!< Non-zero if we are attempting to call a member */ - int max_penalty; /*!< Limit the members that can take this call to this penalty or lower */ - time_t start; /*!< When we started holding */ - time_t expire; /*!< When this entry should expire (time out of queue) */ - struct ast_channel *chan; /*!< Our channel */ - struct queue_ent *next; /*!< The next queue entry */ -}; - -struct member { - char interface[80]; /*!< Technology/Location */ - char membername[80]; /*!< Member name to use in queue logs */ - int penalty; /*!< Are we a last resort? */ - int calls; /*!< Number of calls serviced by this member */ - int dynamic; /*!< Are we dynamically added? */ - int realtime; /*!< Is this member realtime? */ - int status; /*!< Status of queue member */ - int paused; /*!< Are we paused (not accepting calls)? */ - time_t lastcall; /*!< When last successful call was hungup */ - unsigned int dead:1; /*!< Used to detect members deleted in realtime */ - unsigned int delme:1; /*!< Flag to delete entry on reload */ -}; - -struct member_interface { - char interface[80]; - AST_LIST_ENTRY(member_interface) list; /*!< Next call queue */ -}; - -static AST_LIST_HEAD_STATIC(interfaces, member_interface); - -/* values used in multi-bit flags in call_queue */ -#define QUEUE_EMPTY_NORMAL 1 -#define QUEUE_EMPTY_STRICT 2 -#define ANNOUNCEHOLDTIME_ALWAYS 1 -#define ANNOUNCEHOLDTIME_ONCE 2 -#define QUEUE_EVENT_VARIABLES 3 - -struct call_queue { - ast_mutex_t lock; - char name[80]; /*!< Name */ - char moh[80]; /*!< Music On Hold class to be used */ - char announce[80]; /*!< Announcement to play when call is answered */ - char context[AST_MAX_CONTEXT]; /*!< Exit context */ - unsigned int monjoin:1; - unsigned int dead:1; - unsigned int joinempty:2; - unsigned int eventwhencalled:2; - unsigned int leavewhenempty:2; - unsigned int ringinuse:1; - unsigned int setinterfacevar:1; - unsigned int reportholdtime:1; - unsigned int wrapped:1; - unsigned int timeoutrestart:1; - unsigned int announceholdtime:2; - int strategy:4; - unsigned int maskmemberstatus:1; - unsigned int realtime:1; - unsigned int found:1; - int announcefrequency; /*!< How often to announce their position */ - int periodicannouncefrequency; /*!< How often to play periodic announcement */ - int roundingseconds; /*!< How many seconds do we round to? */ - int holdtime; /*!< Current avg holdtime, based on an exponential average */ - int callscompleted; /*!< Number of queue calls completed */ - int callsabandoned; /*!< Number of queue calls abandoned */ - int servicelevel; /*!< seconds setting for servicelevel*/ - int callscompletedinsl; /*!< Number of calls answered with servicelevel*/ - char monfmt[8]; /*!< Format to use when recording calls */ - int montype; /*!< Monitor type Monitor vs. MixMonitor */ - char sound_next[80]; /*!< Sound file: "Your call is now first in line" (def. queue-youarenext) */ - char sound_thereare[80]; /*!< Sound file: "There are currently" (def. queue-thereare) */ - char sound_calls[80]; /*!< Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting)*/ - char sound_holdtime[80]; /*!< Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */ - char sound_minutes[80]; /*!< Sound file: "minutes." (def. queue-minutes) */ - char sound_lessthan[80]; /*!< Sound file: "less-than" (def. queue-lessthan) */ - char sound_seconds[80]; /*!< Sound file: "seconds." (def. queue-seconds) */ - char sound_thanks[80]; /*!< Sound file: "Thank you for your patience." (def. queue-thankyou) */ - char sound_reporthold[80]; /*!< Sound file: "Hold time" (def. queue-reporthold) */ - char sound_periodicannounce[MAX_PERIODIC_ANNOUNCEMENTS][80];/*!< Sound files: Custom announce, no default */ - - int count; /*!< How many entries */ - int maxlen; /*!< Max number of entries */ - int wrapuptime; /*!< Wrapup Time */ - - int retry; /*!< Retry calling everyone after this amount of time */ - int timeout; /*!< How long to wait for an answer */ - int weight; /*!< Respective weight */ - int autopause; /*!< Auto pause queue members if they fail to answer */ - - /* Queue strategy things */ - int rrpos; /*!< Round Robin - position */ - int memberdelay; /*!< Seconds to delay connecting member to caller */ - int autofill; /*!< Ignore the head call status and ring an available agent */ - - struct ao2_container *members; /*!< Head of the list of members */ - /*! - * \brief Number of members _logged in_ - * \note There will be members in the members container that are not logged - * in, so this can not simply be replaced with ao2_container_count(). - */ - int membercount; - struct queue_ent *head; /*!< Head of the list of callers */ - AST_LIST_ENTRY(call_queue) list; /*!< Next call queue */ -}; - -static AST_LIST_HEAD_STATIC(queues, call_queue); - -static int set_member_paused(const char *queuename, const char *interface, int paused); - -static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); - -static void rr_dep_warning(void) -{ - static unsigned int warned = 0; - - if (!warned) { - ast_log(LOG_NOTICE, "The 'roundrobin' queue strategy is deprecated. Please use the 'rrmemory' strategy instead.\n"); - warned = 1; - } -} - -static void monjoin_dep_warning(void) -{ - static unsigned int warned = 0; - if (!warned) { - ast_log(LOG_NOTICE, "The 'monitor-join' queue option is deprecated. Please use monitor-type=mixmonitor instead.\n"); - warned = 1; - } -} -/*! \brief sets the QUEUESTATUS channel variable */ -static void set_queue_result(struct ast_channel *chan, enum queue_result res) -{ - int i; - - for (i = 0; i < sizeof(queue_results) / sizeof(queue_results[0]); i++) { - if (queue_results[i].id == res) { - pbx_builtin_setvar_helper(chan, "QUEUESTATUS", queue_results[i].text); - return; - } - } -} - -static char *int2strat(int strategy) -{ - int x; - - for (x = 0; x < sizeof(strategies) / sizeof(strategies[0]); x++) { - if (strategy == strategies[x].strategy) - return strategies[x].name; - } - - return "<unknown>"; -} - -static int strat2int(const char *strategy) -{ - int x; - - for (x = 0; x < sizeof(strategies) / sizeof(strategies[0]); x++) { - if (!strcasecmp(strategy, strategies[x].name)) - return strategies[x].strategy; - } - - return -1; -} - -/*! \brief Insert the 'new' entry after the 'prev' entry of queue 'q' */ -static inline void insert_entry(struct call_queue *q, struct queue_ent *prev, struct queue_ent *new, int *pos) -{ - struct queue_ent *cur; - - if (!q || !new) - return; - if (prev) { - cur = prev->next; - prev->next = new; - } else { - cur = q->head; - q->head = new; - } - new->next = cur; - new->parent = q; - new->pos = ++(*pos); - new->opos = *pos; -} - -enum queue_member_status { - QUEUE_NO_MEMBERS, - QUEUE_NO_REACHABLE_MEMBERS, - QUEUE_NORMAL -}; - -/*! \brief Check if members are available - * - * This function checks to see if members are available to be called. If any member - * is available, the function immediately returns QUEUE_NORMAL. If no members are available, - * the appropriate reason why is returned - */ -static enum queue_member_status get_member_status(struct call_queue *q, int max_penalty) -{ - struct member *member; - struct ao2_iterator mem_iter; - enum queue_member_status result = QUEUE_NO_MEMBERS; - - ast_mutex_lock(&q->lock); - mem_iter = ao2_iterator_init(q->members, 0); - while ((member = ao2_iterator_next(&mem_iter))) { - if (max_penalty && (member->penalty > max_penalty)) { - ao2_ref(member, -1); - continue; - } - - if (member->paused) { - ao2_ref(member, -1); - continue; - } - - switch (member->status) { - case AST_DEVICE_INVALID: - /* nothing to do */ - ao2_ref(member, -1); - break; - case AST_DEVICE_UNAVAILABLE: - result = QUEUE_NO_REACHABLE_MEMBERS; - ao2_ref(member, -1); - break; - default: - ast_mutex_unlock(&q->lock); - ao2_ref(member, -1); - return QUEUE_NORMAL; - } - } - - ast_mutex_unlock(&q->lock); - return result; -} - -struct statechange { - AST_LIST_ENTRY(statechange) entry; - int state; - char dev[0]; -}; - -static int update_status(const char *interface, const int status) -{ - struct member *cur; - struct ao2_iterator mem_iter; - struct call_queue *q; - - AST_LIST_LOCK(&queues); - AST_LIST_TRAVERSE(&queues, q, list) { - ast_mutex_lock(&q->lock); - mem_iter = ao2_iterator_init(q->members, 0); - while ((cur = ao2_iterator_next(&mem_iter))) { - char *tmp_interface; - char *slash_pos; - tmp_interface = ast_strdupa(cur->interface); - if ((slash_pos = strchr(tmp_interface, '/'))) - if ((slash_pos = strchr(slash_pos + 1, '/'))) - *slash_pos = '\0'; - - if (strcasecmp(interface, tmp_interface)) { - ao2_ref(cur, -1); - continue; - } - - if (cur->status != status) { - cur->status = status; - if (q->maskmemberstatus) { - ao2_ref(cur, -1); - continue; - } - - manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus", - "Queue: %s\r\n" - "Location: %s\r\n" - "MemberName: %s\r\n" - "Membership: %s\r\n" - "Penalty: %d\r\n" - "CallsTaken: %d\r\n" - "LastCall: %d\r\n" - "Status: %d\r\n" - "Paused: %d\r\n", - q->name, cur->interface, cur->membername, cur->dynamic ? "dynamic" : cur->realtime ? "realtime" : "static", - cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused); - } - ao2_ref(cur, -1); - } - ast_mutex_unlock(&q->lock); - } - AST_LIST_UNLOCK(&queues); - - return 0; -} - -/*! \brief set a member's status based on device state of that member's interface*/ -static void *handle_statechange(struct statechange *sc) -{ - struct member_interface *curint; - char *loc; - char *technology; - - technology = ast_strdupa(sc->dev); - loc = strchr(technology, '/'); - if (loc) { - *loc++ = '\0'; - } else { - return NULL; - } - - AST_LIST_LOCK(&interfaces); - AST_LIST_TRAVERSE(&interfaces, curint, list) { - char *interface; - char *slash_pos; - interface = ast_strdupa(curint->interface); - if ((slash_pos = strchr(interface, '/'))) - if ((slash_pos = strchr(slash_pos + 1, '/'))) - *slash_pos = '\0'; - - if (!strcasecmp(interface, sc->dev)) - break; - } - AST_LIST_UNLOCK(&interfaces); - - if (!curint) { - if (option_debug > 2) - ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n", technology, loc, sc->state, devstate2str(sc->state)); - return NULL; - } - - if (option_debug) - ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s)\n", technology, loc, sc->state, devstate2str(sc->state)); - - update_status(sc->dev, sc->state); - - return NULL; -} - -/*! - * \brief Data used by the device state thread - */ -static struct { - /*! Set to 1 to stop the thread */ - unsigned int stop:1; - /*! The device state monitoring thread */ - pthread_t thread; - /*! Lock for the state change queue */ - ast_mutex_t lock; - /*! Condition for the state change queue */ - ast_cond_t cond; - /*! Queue of state changes */ - AST_LIST_HEAD_NOLOCK(, statechange) state_change_q; -} device_state = { - .thread = AST_PTHREADT_NULL, -}; - -/*! \brief Consumer of the statechange queue */ -static void *device_state_thread(void *data) -{ - struct statechange *sc = NULL; - - while (!device_state.stop) { - ast_mutex_lock(&device_state.lock); - if (!(sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry))) { - ast_cond_wait(&device_state.cond, &device_state.lock); - sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry); - } - ast_mutex_unlock(&device_state.lock); - - /* Check to see if we were woken up to see the request to stop */ - if (device_state.stop) - break; - - if (!sc) - continue; - - handle_statechange(sc); - - free(sc); - sc = NULL; - } - - if (sc) - free(sc); - - while ((sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry))) - free(sc); - - return NULL; -} -/*! \brief Producer of the statechange queue */ -static int statechange_queue(const char *dev, int state, void *ign) -{ - struct statechange *sc; - - if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(dev) + 1))) - return 0; - - sc->state = state; - strcpy(sc->dev, dev); - - ast_mutex_lock(&device_state.lock); - AST_LIST_INSERT_TAIL(&device_state.state_change_q, sc, entry); - ast_cond_signal(&device_state.cond); - ast_mutex_unlock(&device_state.lock); - - return 0; -} -/*! \brief allocate space for new queue member and set fields based on parameters passed */ -static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused) -{ - struct member *cur; - - if ((cur = ao2_alloc(sizeof(*cur), NULL))) { - cur->penalty = penalty; - cur->paused = paused; - ast_copy_string(cur->interface, interface, sizeof(cur->interface)); - if (!ast_strlen_zero(membername)) - ast_copy_string(cur->membername, membername, sizeof(cur->membername)); - else - ast_copy_string(cur->membername, interface, sizeof(cur->membername)); - if (!strchr(cur->interface, '/')) - ast_log(LOG_WARNING, "No location at interface '%s'\n", interface); - cur->status = ast_device_state(interface); - } - - return cur; -} - -static struct call_queue *alloc_queue(const char *queuename) -{ - struct call_queue *q; - - if ((q = ast_calloc(1, sizeof(*q)))) { - ast_mutex_init(&q->lock); - ast_copy_string(q->name, queuename, sizeof(q->name)); - } - return q; -} - -static int compress_char(const char c) -{ - if (c < 32) - return 0; - else if (c > 96) - return c - 64; - else - return c - 32; -} - -static int member_hash_fn(const void *obj, const int flags) -{ - const struct member *mem = obj; - const char *chname = strchr(mem->interface, '/'); - int ret = 0, i; - if (!chname) - chname = mem->interface; - for (i = 0; i < 5 && chname[i]; i++) - ret += compress_char(chname[i]) << (i * 6); - return ret; -} - -static int member_cmp_fn(void *obj1, void *obj2, int flags) -{ - struct member *mem1 = obj1, *mem2 = obj2; - return strcmp(mem1->interface, mem2->interface) ? 0 : CMP_MATCH | CMP_STOP; -} - -static void init_queue(struct call_queue *q) -{ - int i; - - q->dead = 0; - q->retry = DEFAULT_RETRY; - q->timeout = -1; - q->maxlen = 0; - q->announcefrequency = 0; - q->announceholdtime = 0; - q->roundingseconds = 0; /* Default - don't announce seconds */ - q->servicelevel = 0; - q->ringinuse = 1; - q->setinterfacevar = 0; - q->autofill = autofill_default; - q->montype = montype_default; - q->moh[0] = '\0'; - q->announce[0] = '\0'; - q->context[0] = '\0'; - q->monfmt[0] = '\0'; - q->periodicannouncefrequency = 0; - q->reportholdtime = 0; - q->monjoin = 0; - q->wrapuptime = 0; - q->joinempty = 0; - q->leavewhenempty = 0; - q->memberdelay = 0; - q->maskmemberstatus = 0; - q->eventwhencalled = 0; - q->weight = 0; - q->timeoutrestart = 0; - if (!q->members) - q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn); - q->membercount = 0; - q->found = 1; - ast_copy_string(q->sound_next, "queue-youarenext", sizeof(q->sound_next)); - ast_copy_string(q->sound_thereare, "queue-thereare", sizeof(q->sound_thereare)); - ast_copy_string(q->sound_calls, "queue-callswaiting", sizeof(q->sound_calls)); - ast_copy_string(q->sound_holdtime, "queue-holdtime", sizeof(q->sound_holdtime)); - ast_copy_string(q->sound_minutes, "queue-minutes", sizeof(q->sound_minutes)); - ast_copy_string(q->sound_seconds, "queue-seconds", sizeof(q->sound_seconds)); - ast_copy_string(q->sound_thanks, "queue-thankyou", sizeof(q->sound_thanks)); - ast_copy_string(q->sound_lessthan, "queue-less-than", sizeof(q->sound_lessthan)); - ast_copy_string(q->sound_reporthold, "queue-reporthold", sizeof(q->sound_reporthold)); - ast_copy_string(q->sound_periodicannounce[0], "queue-periodic-announce", sizeof(q->sound_periodicannounce[0])); - for (i = 1; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) { - q->sound_periodicannounce[i][0]='\0'; - } -} - -static void clear_queue(struct call_queue *q) -{ - q->holdtime = 0; - q->callscompleted = 0; - q->callsabandoned = 0; - q->callscompletedinsl = 0; - q->wrapuptime = 0; -} - -static int add_to_interfaces(const char *interface) -{ - struct member_interface *curint; - - AST_LIST_LOCK(&interfaces); - AST_LIST_TRAVERSE(&interfaces, curint, list) { - if (!strcasecmp(curint->interface, interface)) - break; - } - - if (curint) { - AST_LIST_UNLOCK(&interfaces); - return 0; - } - - if (option_debug) - ast_log(LOG_DEBUG, "Adding %s to the list of interfaces that make up all of our queue members.\n", interface); - - if ((curint = ast_calloc(1, sizeof(*curint)))) { - ast_copy_string(curint->interface, interface, sizeof(curint->interface)); - AST_LIST_INSERT_HEAD(&interfaces, curint, list); - } - AST_LIST_UNLOCK(&interfaces); - - return 0; -} - -static int interface_exists_global(const char *interface) -{ - struct call_queue *q; - struct member *mem, tmpmem; - int ret = 0; - - ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); - - AST_LIST_LOCK(&queues); - AST_LIST_TRAVERSE(&queues, q, list) { - ast_mutex_lock(&q->lock); - if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) { - ao2_ref(mem, -1); - ret = 1; - } - ast_mutex_unlock(&q->lock); - if (ret) - break; - } - AST_LIST_UNLOCK(&queues); - - return ret; -} - -static int remove_from_interfaces(const char *interface) -{ - struct member_interface *curint; - - if (interface_exists_global(interface)) - return 0; - - AST_LIST_LOCK(&interfaces); - AST_LIST_TRAVERSE_SAFE_BEGIN(&interfaces, curint, list) { - if (!strcasecmp(curint->interface, interface)) { - if (option_debug) - ast_log(LOG_DEBUG, "Removing %s from the list of interfaces that make up all of our queue members.\n", interface); - AST_LIST_REMOVE_CURRENT(&interfaces, list); - free(curint); - break; - } - } - AST_LIST_TRAVERSE_SAFE_END; - AST_LIST_UNLOCK(&interfaces); - - return 0; -} - -static void clear_and_free_interfaces(void) -{ - struct member_interface *curint; - - AST_LIST_LOCK(&interfaces); - while ((curint = AST_LIST_REMOVE_HEAD(&interfaces, list))) - free(curint); - AST_LIST_UNLOCK(&interfaces); -} - -/*! \brief Configure a queue parameter. -\par - For error reporting, line number is passed for .conf static configuration. - For Realtime queues, linenum is -1. - The failunknown flag is set for config files (and static realtime) to show - errors for unknown parameters. It is cleared for dynamic realtime to allow - extra fields in the tables. */ -static void queue_set_param(struct call_queue *q, const char *param, const char *val, int linenum, int failunknown) -{ - if (!strcasecmp(param, "musicclass") || - !strcasecmp(param, "music") || !strcasecmp(param, "musiconhold")) { - ast_copy_string(q->moh, val, sizeof(q->moh)); - } else if (!strcasecmp(param, "announce")) { - ast_copy_string(q->announce, val, sizeof(q->announce)); - } else if (!strcasecmp(param, "context")) { - ast_copy_string(q->context, val, sizeof(q->context)); - } else if (!strcasecmp(param, "timeout")) { - q->timeout = atoi(val); - if (q->timeout < 0) - q->timeout = DEFAULT_TIMEOUT; - } else if (!strcasecmp(param, "ringinuse")) { - q->ringinuse = ast_true(val); - } else if (!strcasecmp(param, "setinterfacevar")) { - q->setinterfacevar = ast_true(val); - } else if (!strcasecmp(param, "monitor-join")) { - monjoin_dep_warning(); - q->monjoin = ast_true(val); - } else if (!strcasecmp(param, "monitor-format")) { - ast_copy_string(q->monfmt, val, sizeof(q->monfmt)); - } else if (!strcasecmp(param, "queue-youarenext")) { - ast_copy_string(q->sound_next, val, sizeof(q->sound_next)); - } else if (!strcasecmp(param, "queue-thereare")) { - ast_copy_string(q->sound_thereare, val, sizeof(q->sound_thereare)); - } else if (!strcasecmp(param, "queue-callswaiting")) { - ast_copy_string(q->sound_calls, val, sizeof(q->sound_calls)); - } else if (!strcasecmp(param, "queue-holdtime")) { - ast_copy_string(q->sound_holdtime, val, sizeof(q->sound_holdtime)); - } else if (!strcasecmp(param, "queue-minutes")) { - ast_copy_string(q->sound_minutes, val, sizeof(q->sound_minutes)); - } else if (!strcasecmp(param, "queue-seconds")) { - ast_copy_string(q->sound_seconds, val, sizeof(q->sound_seconds)); - } else if (!strcasecmp(param, "queue-lessthan")) { - ast_copy_string(q->sound_lessthan, val, sizeof(q->sound_lessthan)); - } else if (!strcasecmp(param, "queue-thankyou")) { - ast_copy_string(q->sound_thanks, val, sizeof(q->sound_thanks)); - } else if (!strcasecmp(param, "queue-reporthold")) { - ast_copy_string(q->sound_reporthold, val, sizeof(q->sound_reporthold)); - } else if (!strcasecmp(param, "announce-frequency")) { - q->announcefrequency = atoi(val); - } else if (!strcasecmp(param, "announce-round-seconds")) { - q->roundingseconds = atoi(val); - if (q->roundingseconds>60 || q->roundingseconds<0) { - if (linenum >= 0) { - ast_log(LOG_WARNING, "'%s' isn't a valid value for %s " - "using 0 instead for queue '%s' at line %d of queues.conf\n", - val, param, q->name, linenum); - } else { - ast_log(LOG_WARNING, "'%s' isn't a valid value for %s " - "using 0 instead for queue '%s'\n", val, param, q->name); - } - q->roundingseconds=0; - } - } else if (!strcasecmp(param, "announce-holdtime")) { - if (!strcasecmp(val, "once")) - q->announceholdtime = ANNOUNCEHOLDTIME_ONCE; - else if (ast_true(val)) - q->announceholdtime = ANNOUNCEHOLDTIME_ALWAYS; - else - q->announceholdtime = 0; - } else if (!strcasecmp(param, "periodic-announce")) { - if (strchr(val, '|')) { - char *s, *buf = ast_strdupa(val); - unsigned int i = 0; - - while ((s = strsep(&buf, "|"))) { - ast_copy_string(q->sound_periodicannounce[i], s, sizeof(q->sound_periodicannounce[i])); - i++; - if (i == MAX_PERIODIC_ANNOUNCEMENTS) - break; - } - } else { - ast_copy_string(q->sound_periodicannounce[0], val, sizeof(q->sound_periodicannounce[0])); - } - } else if (!strcasecmp(param, "periodic-announce-frequency")) { - q->periodicannouncefrequency = atoi(val); - } else if (!strcasecmp(param, "retry")) { - q->retry = atoi(val); - if (q->retry <= 0) - q->retry = DEFAULT_RETRY; - } else if (!strcasecmp(param, "wrapuptime")) { - q->wrapuptime = atoi(val); - } else if (!strcasecmp(param, "autofill")) { - q->autofill = ast_true(val); - } else if (!strcasecmp(param, "monitor-type")) { - if (!strcasecmp(val, "mixmonitor")) - q->montype = 1; - } else if (!strcasecmp(param, "autopause")) { - q->autopause = ast_true(val); - } else if (!strcasecmp(param, "maxlen")) { - q->maxlen = atoi(val); - if (q->maxlen < 0) - q->maxlen = 0; - } else if (!strcasecmp(param, "servicelevel")) { - q->servicelevel= atoi(val); - } else if (!strcasecmp(param, "strategy")) { - q->strategy = strat2int(val); - if (q->strategy < 0) { - ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n", - val, q->name); - q->strategy = QUEUE_STRATEGY_RINGALL; - } - } else if (!strcasecmp(param, "joinempty")) { - if (!strcasecmp(val, "strict")) - q->joinempty = QUEUE_EMPTY_STRICT; - else if (ast_true(val)) - q->joinempty = QUEUE_EMPTY_NORMAL; - else - q->joinempty = 0; - } else if (!strcasecmp(param, "leavewhenempty")) { - if (!strcasecmp(val, "strict")) - q->leavewhenempty = QUEUE_EMPTY_STRICT; - else if (ast_true(val)) - q->leavewhenempty = QUEUE_EMPTY_NORMAL; - else - q->leavewhenempty = 0; - } else if (!strcasecmp(param, "eventmemberstatus")) { - q->maskmemberstatus = !ast_true(val); - } else if (!strcasecmp(param, "eventwhencalled")) { - if (!strcasecmp(val, "vars")) { - q->eventwhencalled = QUEUE_EVENT_VARIABLES; - } else { - q->eventwhencalled = ast_true(val) ? 1 : 0; - } - } else if (!strcasecmp(param, "reportholdtime")) { - q->reportholdtime = ast_true(val); - } else if (!strcasecmp(param, "memberdelay")) { - q->memberdelay = atoi(val); - } else if (!strcasecmp(param, "weight")) { - q->weight = atoi(val); - if (q->weight) - use_weight++; - /* With Realtime queues, if the last queue using weights is deleted in realtime, - we will not see any effect on use_weight until next reload. */ - } else if (!strcasecmp(param, "timeoutrestart")) { - q->timeoutrestart = ast_true(val); - } else if (failunknown) { - if (linenum >= 0) { - ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queues.conf\n", - q->name, param, linenum); - } else { - ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s\n", q->name, param); - } - } -} - -static void rt_handle_member_record(struct call_queue *q, char *interface, const char *membername, const char *penalty_str, const char *paused_str) -{ - struct member *m, tmpmem; - int penalty = 0; - int paused = 0; - - if (penalty_str) { - penalty = atoi(penalty_str); - if (penalty < 0) - penalty = 0; - } - - if (paused_str) { - paused = atoi(paused_str); - if (paused < 0) - paused = 0; - } - - /* Find the member, or the place to put a new one. */ - ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); - m = ao2_find(q->members, &tmpmem, OBJ_POINTER); - - /* Create a new one if not found, else update penalty */ - if (!m) { - if ((m = create_queue_member(interface, membername, penalty, paused))) { - m->dead = 0; - m->realtime = 1; - add_to_interfaces(interface); - ao2_link(q->members, m); - ao2_ref(m, -1); - m = NULL; - q->membercount++; - } - } else { - m->dead = 0; /* Do not delete this one. */ - if (paused_str) - m->paused = paused; - m->penalty = penalty; - ao2_ref(m, -1); - } -} - -static void free_members(struct call_queue *q, int all) -{ - /* Free non-dynamic members */ - struct member *cur; - struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0); - - while ((cur = ao2_iterator_next(&mem_iter))) { - if (all || !cur->dynamic) { - ao2_unlink(q->members, cur); - remove_from_interfaces(cur->interface); - q->membercount--; - } - ao2_ref(cur, -1); - } -} - -static void destroy_queue(struct call_queue *q) -{ - free_members(q, 1); - ast_mutex_destroy(&q->lock); - ao2_ref(q->members, -1); - free(q); -} - -/*!\brief Reload a single queue via realtime. - \return Return the queue, or NULL if it doesn't exist. - \note Should be called with the global qlock locked. */ -static struct call_queue *find_queue_by_name_rt(const char *queuename, struct ast_variable *queue_vars, struct ast_config *member_config) -{ - struct ast_variable *v; - struct call_queue *q; - struct member *m; - struct ao2_iterator mem_iter; - char *interface = NULL; - char *tmp, *tmp_name; - char tmpbuf[64]; /* Must be longer than the longest queue param name. */ - - /* Find the queue in the in-core list (we will create a new one if not found). */ - AST_LIST_TRAVERSE(&queues, q, list) { - if (!strcasecmp(q->name, queuename)) - break; - } - - /* Static queues override realtime. */ - if (q) { - ast_mutex_lock(&q->lock); - if (!q->realtime) { - if (q->dead) { - ast_mutex_unlock(&q->lock); - return NULL; - } else { - ast_log(LOG_WARNING, "Static queue '%s' already exists. Not loading from realtime\n", q->name); - ast_mutex_unlock(&q->lock); - return q; - } - } - } else if (!member_config) - /* Not found in the list, and it's not realtime ... */ - return NULL; - - /* Check if queue is defined in realtime. */ - if (!queue_vars) { - /* Delete queue from in-core list if it has been deleted in realtime. */ - if (q) { - /*! \note Hmm, can't seem to distinguish a DB failure from a not - found condition... So we might delete an in-core queue - in case of DB failure. */ - ast_log(LOG_DEBUG, "Queue %s not found in realtime.\n", queuename); - - q->dead = 1; - /* Delete if unused (else will be deleted when last caller leaves). */ - if (!q->count) { - /* Delete. */ - AST_LIST_REMOVE(&queues, q, list); - ast_mutex_unlock(&q->lock); - destroy_queue(q); - } else - ast_mutex_unlock(&q->lock); - } - return NULL; - } - - /* Create a new queue if an in-core entry does not exist yet. */ - if (!q) { - if (!(q = alloc_queue(queuename))) - return NULL; - ast_mutex_lock(&q->lock); - clear_queue(q); - q->realtime = 1; - AST_LIST_INSERT_HEAD(&queues, q, list); - } - init_queue(q); /* Ensure defaults for all parameters not set explicitly. */ - - memset(tmpbuf, 0, sizeof(tmpbuf)); - for (v = queue_vars; v; v = v->next) { - /* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */ - if ((tmp = strchr(v->name, '_'))) { - ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf)); - tmp_name = tmpbuf; - tmp = tmp_name; - while ((tmp = strchr(tmp, '_'))) - *tmp++ = '-'; - } else - tmp_name = v->name; - - if (!ast_strlen_zero(v->value)) { - /* Don't want to try to set the option if the value is empty */ - queue_set_param(q, tmp_name, v->value, -1, 0); - } - } - - if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN) - rr_dep_warning(); - - /* Temporarily set realtime members dead so we can detect deleted ones. - * Also set the membercount correctly for realtime*/ - mem_iter = ao2_iterator_init(q->members, 0); - while ((m = ao2_iterator_next(&mem_iter))) { - q->membercount++; - if (m->realtime) - m->dead = 1; - ao2_ref(m, -1); - } - - while ((interface = ast_category_browse(member_config, interface))) { - rt_handle_member_record(q, interface, - ast_variable_retrieve(member_config, interface, "membername"), - ast_variable_retrieve(member_config, interface, "penalty"), - ast_variable_retrieve(member_config, interface, "paused")); - } - - /* Delete all realtime members that have been deleted in DB. */ - mem_iter = ao2_iterator_init(q->members, 0); - while ((m = ao2_iterator_next(&mem_iter))) { - if (m->dead) { - ao2_unlink(q->members, m); - ast_mutex_unlock(&q->lock); - remove_from_interfaces(m->interface); - ast_mutex_lock(&q->lock); - q->membercount--; - } - ao2_ref(m, -1); - } - - ast_mutex_unlock(&q->lock); - - return q; -} - -static int update_realtime_member_field(struct member *mem, const char *queue_name, const char *field, const char *value) -{ - struct ast_variable *var, *save; - int ret = -1; - - if (!(var = ast_load_realtime("queue_members", "interface", mem->interface, "queue_name", queue_name, NULL))) - return ret; - save = var; - while (var) { - if (!strcmp(var->name, "uniqueid")) - break; - var = var->next; - } - if (var && !ast_strlen_zero(var->value)) { - if ((ast_update_realtime("queue_members", "uniqueid", var->value, field, value, NULL)) > -1) - ret = 0; - } - ast_variables_destroy(save); - return ret; -} - -static void update_realtime_members(struct call_queue *q) -{ - struct ast_config *member_config = NULL; - struct member *m; - char *interface = NULL; - struct ao2_iterator mem_iter; - - if (!(member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", q->name , NULL))) { - /*This queue doesn't have realtime members*/ - if (option_debug > 2) - ast_log(LOG_DEBUG, "Queue %s has no realtime members defined. No need for update\n", q->name); - return; - } - - ast_mutex_lock(&q->lock); - - /* Temporarily set realtime members dead so we can detect deleted ones.*/ - mem_iter = ao2_iterator_init(q->members, 0); - while ((m = ao2_iterator_next(&mem_iter))) { - if (m->realtime) - m->dead = 1; - ao2_ref(m, -1); - } - - while ((interface = ast_category_browse(member_config, interface))) { - rt_handle_member_record(q, interface, - S_OR(ast_variable_retrieve(member_config, interface, "membername"), interface), - ast_variable_retrieve(member_config, interface, "penalty"), - ast_variable_retrieve(member_config, interface, "paused")); - } - - /* Delete all realtime members that have been deleted in DB. */ - mem_iter = ao2_iterator_init(q->members, 0); - while ((m = ao2_iterator_next(&mem_iter))) { - if (m->dead) { - ao2_unlink(q->members, m); - ast_mutex_unlock(&q->lock); - remove_from_interfaces(m->interface); - ast_mutex_lock(&q->lock); - q->membercount--; - } - ao2_ref(m, -1); - } - ast_mutex_unlock(&q->lock); - ast_config_destroy(member_config); -} - -static struct call_queue *load_realtime_queue(const char *queuename) -{ - struct ast_variable *queue_vars; - struct ast_config *member_config = NULL; - struct call_queue *q; - - /* Find the queue in the in-core list first. */ - AST_LIST_LOCK(&queues); - AST_LIST_TRAVERSE(&queues, q, list) { - if (!strcasecmp(q->name, queuename)) { - break; - } - } - AST_LIST_UNLOCK(&queues); - - if (!q || q->realtime) { - /*! \note Load from realtime before taking the global qlock, to avoid blocking all - queue operations while waiting for the DB. - - This will be two separate database transactions, so we might - see queue parameters as they were before another process - changed the queue and member list as it was after the change. - Thus we might see an empty member list when a queue is - deleted. In practise, this is unlikely to cause a problem. */ - - queue_vars = ast_load_realtime("queues", "name", queuename, NULL); - if (queue_vars) { - member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", queuename, NULL); - if (!member_config) { - ast_log(LOG_ERROR, "no queue_members defined in your config (extconfig.conf).\n"); - ast_variables_destroy(queue_vars); - return NULL; - } - } - - AST_LIST_LOCK(&queues); - - q = find_queue_by_name_rt(queuename, queue_vars, member_config); - if (member_config) - ast_config_destroy(member_config); - if (queue_vars) - ast_variables_destroy(queue_vars); - - AST_LIST_UNLOCK(&queues); - } else { - update_realtime_members(q); - } - return q; -} - -static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason) -{ - struct call_queue *q; - struct queue_ent *cur, *prev = NULL; - int res = -1; - int pos = 0; - int inserted = 0; - enum queue_member_status stat; - - if (!(q = load_realtime_queue(queuename))) - return res; - - AST_LIST_LOCK(&queues); - ast_mutex_lock(&q->lock); - - /* This is our one */ - stat = get_member_status(q, qe->max_penalty); - if (!q->joinempty && (stat == QUEUE_NO_MEMBERS)) - *reason = QUEUE_JOINEMPTY; - else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_MEMBERS)) - *reason = QUEUE_JOINUNAVAIL; - else if (q->maxlen && (q->count >= q->maxlen)) - *reason = QUEUE_FULL; - else { - /* There's space for us, put us at the right position inside - * the queue. - * Take into account the priority of the calling user */ - inserted = 0; - prev = NULL; - cur = q->head; - while (cur) { - /* We have higher priority than the current user, enter - * before him, after all the other users with priority - * higher or equal to our priority. */ - if ((!inserted) && (qe->prio > cur->prio)) { - insert_entry(q, prev, qe, &pos); - inserted = 1; - } - cur->pos = ++pos; - prev = cur; - cur = cur->next; - } - /* No luck, join at the end of the queue */ - if (!inserted) - insert_entry(q, prev, qe, &pos); - ast_copy_string(qe->moh, q->moh, sizeof(qe->moh)); - ast_copy_string(qe->announce, q->announce, sizeof(qe->announce)); - ast_copy_string(qe->context, q->context, sizeof(qe->context)); - q->count++; - res = 0; - manager_event(EVENT_FLAG_CALL, "Join", - "Channel: %s\r\nCallerID: %s\r\nCallerIDName: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\nUniqueid: %s\r\n", - qe->chan->name, - S_OR(qe->chan->cid.cid_num, "unknown"), /* XXX somewhere else it is <unknown> */ - S_OR(qe->chan->cid.cid_name, "unknown"), - q->name, qe->pos, q->count, qe->chan->uniqueid ); - if (option_debug) - ast_log(LOG_DEBUG, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos ); - } - ast_mutex_unlock(&q->lock); - AST_LIST_UNLOCK(&queues); - - return res; -} - -static int play_file(struct ast_channel *chan, char *filename) -{ - int res; - - ast_stopstream(chan); - - res = ast_streamfile(chan, filename, chan->language); - if (!res) - res = ast_waitstream(chan, AST_DIGIT_ANY); - - ast_stopstream(chan); - - return res; -} - -static int valid_exit(struct queue_ent *qe, char digit) -{ - int digitlen = strlen(qe->digits); - - /* Prevent possible buffer overflow */ - if (digitlen < sizeof(qe->digits) - 2) { - qe->digits[digitlen] = digit; - qe->digits[digitlen + 1] = '\0'; - } else { - qe->digits[0] = '\0'; - return 0; - } - - /* If there's no context to goto, short-circuit */ - if (ast_strlen_zero(qe->context)) - return 0; - - /* If the extension is bad, then reset the digits to blank */ - if (!ast_canmatch_extension(qe->chan, qe->context, qe->digits, 1, qe->chan->cid.cid_num)) { - qe->digits[0] = '\0'; - return 0; - } - - /* We have an exact match */ - if (!ast_goto_if_exists(qe->chan, qe->context, qe->digits, 1)) { - qe->valid_digits = 1; - /* Return 1 on a successful goto */ - return 1; - } - - return 0; -} - -static int say_position(struct queue_ent *qe) -{ - int res = 0, avgholdmins, avgholdsecs; - time_t now; - - /* Check to see if this is ludicrous -- if we just announced position, don't do it again*/ - time(&now); - if ((now - qe->last_pos) < 15) - return 0; - - /* If either our position has changed, or we are over the freq timer, say position */ - if ((qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency)) - return 0; - - ast_moh_stop(qe->chan); - /* Say we're next, if we are */ - if (qe->pos == 1) { - res = play_file(qe->chan, qe->parent->sound_next); - if (res) - goto playout; - else - goto posout; - } else { - res = play_file(qe->chan, qe->parent->sound_thereare); - if (res) - goto playout; - res = ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language, (char *) NULL); /* Needs gender */ - if (res) - goto playout; - res = play_file(qe->chan, qe->parent->sound_calls); - if (res) - goto playout; - } - /* Round hold time to nearest minute */ - avgholdmins = abs(((qe->parent->holdtime + 30) - (now - qe->start)) / 60); - - /* If they have specified a rounding then round the seconds as well */ - if (qe->parent->roundingseconds) { - avgholdsecs = (abs(((qe->parent->holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds; - avgholdsecs *= qe->parent->roundingseconds; - } else { - avgholdsecs = 0; - } - - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes %d seconds\n", qe->parent->name, avgholdmins, avgholdsecs); - - /* If the hold time is >1 min, if it's enabled, and if it's not - supposed to be only once and we have already said it, say it */ - if ((avgholdmins+avgholdsecs) > 0 && qe->parent->announceholdtime && - ((qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE && !qe->last_pos) || - !(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE))) { - res = play_file(qe->chan, qe->parent->sound_holdtime); - if (res) - goto playout; - - if (avgholdmins > 0) { - if (avgholdmins < 2) { - res = play_file(qe->chan, qe->parent->sound_lessthan); - if (res) - goto playout; - - res = ast_say_number(qe->chan, 2, AST_DIGIT_ANY, qe->chan->language, NULL); - if (res) - goto playout; - } else { - res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, NULL); - if (res) - goto playout; - } - - res = play_file(qe->chan, qe->parent->sound_minutes); - if (res) - goto playout; - } - if (avgholdsecs>0) { - res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, NULL); - if (res) - goto playout; - - res = play_file(qe->chan, qe->parent->sound_seconds); - if (res) - goto playout; - } - - } - -posout: - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n", - qe->chan->name, qe->parent->name, qe->pos); - res = play_file(qe->chan, qe->parent->sound_thanks); - -playout: - if ((res > 0 && !valid_exit(qe, res)) || res < 0) - res = 0; - - /* Set our last_pos indicators */ - qe->last_pos = now; - qe->last_pos_said = qe->pos; - - /* Don't restart music on hold if we're about to exit the caller from the queue */ - if (!res) - ast_moh_start(qe->chan, qe->moh, NULL); - - return res; -} - -static void recalc_holdtime(struct queue_ent *qe, int newholdtime) -{ - int oldvalue; - - /* Calculate holdtime using an exponential average */ - /* Thanks to SRT for this contribution */ - /* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */ - - ast_mutex_lock(&qe->parent->lock); - oldvalue = qe->parent->holdtime; - qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2; - ast_mutex_unlock(&qe->parent->lock); -} - - -static void leave_queue(struct queue_ent *qe) -{ - struct call_queue *q; - struct queue_ent *cur, *prev = NULL; - int pos = 0; - - if (!(q = qe->parent)) - return; - ast_mutex_lock(&q->lock); - - prev = NULL; - for (cur = q->head; cur; cur = cur->next) { - if (cur == qe) { - q->count--; - - /* Take us out of the queue */ - manager_event(EVENT_FLAG_CALL, "Leave", - "Channel: %s\r\nQueue: %s\r\nCount: %d\r\nUniqueid: %s\r\n", - qe->chan->name, q->name, q->count, qe->chan->uniqueid); - if (option_debug) - ast_log(LOG_DEBUG, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name ); - /* Take us out of the queue */ - if (prev) - prev->next = cur->next; - else - q->head = cur->next; - } else { - /* Renumber the people after us in the queue based on a new count */ - cur->pos = ++pos; - prev = cur; - } - } - ast_mutex_unlock(&q->lock); - - if (q->dead && !q->count) { - /* It's dead and nobody is in it, so kill it */ - AST_LIST_LOCK(&queues); - AST_LIST_REMOVE(&queues, q, list); - AST_LIST_UNLOCK(&queues); - destroy_queue(q); - } -} - -/* Hang up a list of outgoing calls */ -static void hangupcalls(struct callattempt *outgoing, struct ast_channel *exception) -{ - struct callattempt *oo; - - while (outgoing) { - /* Hangup any existing lines we have open */ - if (outgoing->chan && (outgoing->chan != exception)) - ast_hangup(outgoing->chan); - oo = outgoing; - outgoing = outgoing->q_next; - if (oo->member) - ao2_ref(oo->member, -1); - free(oo); - } -} - - -/* traverse all defined queues which have calls waiting and contain this member - return 0 if no other queue has precedence (higher weight) or 1 if found */ -static int compare_weight(struct call_queue *rq, struct member *member) -{ - struct call_queue *q; - struct member *mem; - int found = 0; - - /* &qlock and &rq->lock already set by try_calling() - * to solve deadlock */ - AST_LIST_TRAVERSE(&queues, q, list) { - if (q == rq) /* don't check myself, could deadlock */ - continue; - ast_mutex_lock(&q->lock); - if (q->count && q->members) { - if ((mem = ao2_find(q->members, member, OBJ_POINTER))) { - ast_log(LOG_DEBUG, "Found matching member %s in queue '%s'\n", mem->interface, q->name); - if (q->weight > rq->weight) { - ast_log(LOG_DEBUG, "Queue '%s' (weight %d, calls %d) is preferred over '%s' (weight %d, calls %d)\n", q->name, q->weight, q->count, rq->name, rq->weight, rq->count); - found = 1; - } - ao2_ref(mem, -1); - } - } - ast_mutex_unlock(&q->lock); - if (found) - break; - } - return found; -} - -/*! \brief common hangup actions */ -static void do_hang(struct callattempt *o) -{ - o->stillgoing = 0; - ast_hangup(o->chan); - o->chan = NULL; -} - -static char *vars2manager(struct ast_channel *chan, char *vars, size_t len) -{ - char *tmp = alloca(len); - - if (pbx_builtin_serialize_variables(chan, tmp, len)) { - int i, j; - - /* convert "\n" to "\nVariable: " */ - strcpy(vars, "Variable: "); - - for (i = 0, j = 10; (i < len - 1) && (j < len - 1); i++, j++) { - vars[j] = tmp[i]; - - if (tmp[i + 1] == '\0') - break; - if (tmp[i] == '\n') { - vars[j++] = '\r'; - vars[j++] = '\n'; - - ast_copy_string(&(vars[j]), "Variable: ", len - j); - j += 9; - } - } - if (j > len - 3) - j = len - 3; - vars[j++] = '\r'; - vars[j++] = '\n'; - vars[j] = '\0'; - } else { - /* there are no channel variables; leave it blank */ - *vars = '\0'; - } - return vars; -} - -/*! \brief Part 2 of ring_one - * - * Does error checking before attempting to request a channel and call a member. This - * function is only called from ring_one - */ -static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies) -{ - int res; - int status; - char tech[256]; - char *location; - const char *macrocontext, *macroexten; - - /* on entry here, we know that tmp->chan == NULL */ - if (qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime)) { - if (option_debug) - ast_log(LOG_DEBUG, "Wrapuptime not yet expired for %s\n", tmp->interface); - if (qe->chan->cdr) - ast_cdr_busy(qe->chan->cdr); - tmp->stillgoing = 0; - (*busies)++; - return 0; - } - - if (!qe->parent->ringinuse && (tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) { - if (option_debug) - ast_log(LOG_DEBUG, "%s in use, can't receive call\n", tmp->interface); - if (qe->chan->cdr) - ast_cdr_busy(qe->chan->cdr); - tmp->stillgoing = 0; - return 0; - } - - if (tmp->member->paused) { - if (option_debug) - ast_log(LOG_DEBUG, "%s paused, can't receive call\n", tmp->interface); - if (qe->chan->cdr) - ast_cdr_busy(qe->chan->cdr); - tmp->stillgoing = 0; - return 0; - } - if (use_weight && compare_weight(qe->parent,tmp->member)) { - ast_log(LOG_DEBUG, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface); - if (qe->chan->cdr) - ast_cdr_busy(qe->chan->cdr); - tmp->stillgoing = 0; - (*busies)++; - return 0; - } - - ast_copy_string(tech, tmp->interface, sizeof(tech)); - if ((location = strchr(tech, '/'))) - *location++ = '\0'; - else - location = ""; - - /* Request the peer */ - tmp->chan = ast_request(tech, qe->chan->nativeformats, location, &status); - if (!tmp->chan) { /* If we can't, just go on to the next call */ - if (qe->chan->cdr) - ast_cdr_busy(qe->chan->cdr); - tmp->stillgoing = 0; - - update_status(tmp->member->interface, ast_device_state(tmp->member->interface)); - - ast_mutex_lock(&qe->parent->lock); - qe->parent->rrpos++; - ast_mutex_unlock(&qe->parent->lock); - - (*busies)++; - return 0; - } - - tmp->chan->appl = "AppQueue"; - tmp->chan->data = "(Outgoing Line)"; - tmp->chan->whentohangup = 0; - if (tmp->chan->cid.cid_num) - free(tmp->chan->cid.cid_num); - tmp->chan->cid.cid_num = ast_strdup(qe->chan->cid.cid_num); - if (tmp->chan->cid.cid_name) - free(tmp->chan->cid.cid_name); - tmp->chan->cid.cid_name = ast_strdup(qe->chan->cid.cid_name); - if (tmp->chan->cid.cid_ani) - free(tmp->chan->cid.cid_ani); - tmp->chan->cid.cid_ani = ast_strdup(qe->chan->cid.cid_ani); - - /* Inherit specially named variables from parent channel */ - ast_channel_inherit_variables(qe->chan, tmp->chan); - - /* Presense of ADSI CPE on outgoing channel follows ours */ - tmp->chan->adsicpe = qe->chan->adsicpe; - - /* Inherit context and extension */ - ast_channel_lock(qe->chan); - macrocontext = pbx_builtin_getvar_helper(qe->chan, "MACRO_CONTEXT"); - if (!ast_strlen_zero(macrocontext)) - ast_copy_string(tmp->chan->dialcontext, macrocontext, sizeof(tmp->chan->dialcontext)); - else - ast_copy_string(tmp->chan->dialcontext, qe->chan->context, sizeof(tmp->chan->dialcontext)); - macroexten = pbx_builtin_getvar_helper(qe->chan, "MACRO_EXTEN"); - if (!ast_strlen_zero(macroexten)) - ast_copy_string(tmp->chan->exten, macroexten, sizeof(tmp->chan->exten)); - else - ast_copy_string(tmp->chan->exten, qe->chan->exten, sizeof(tmp->chan->exten)); - ast_channel_unlock(qe->chan); - - /* Place the call, but don't wait on the answer */ - if ((res = ast_call(tmp->chan, location, 0))) { - /* Again, keep going even if there's an error */ - if (option_debug) - ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res); - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->interface); - do_hang(tmp); - (*busies)++; - update_status(tmp->member->interface, ast_device_state(tmp->member->interface)); - return 0; - } else if (qe->parent->eventwhencalled) { - char vars[2048]; - - manager_event(EVENT_FLAG_AGENT, "AgentCalled", - "AgentCalled: %s\r\n" - "AgentName: %s\r\n" - "ChannelCalling: %s\r\n" - "CallerID: %s\r\n" - "CallerIDName: %s\r\n" - "Context: %s\r\n" - "Extension: %s\r\n" - "Priority: %d\r\n" - "%s", - tmp->interface, tmp->member->membername, qe->chan->name, - tmp->chan->cid.cid_num ? tmp->chan->cid.cid_num : "unknown", - tmp->chan->cid.cid_name ? tmp->chan->cid.cid_name : "unknown", - qe->chan->context, qe->chan->exten, qe->chan->priority, - qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->interface); - } - - update_status(tmp->member->interface, ast_device_state(tmp->member->interface)); - return 1; -} - -/*! \brief find the entry with the best metric, or NULL */ -static struct callattempt *find_best(struct callattempt *outgoing) -{ - struct callattempt *best = NULL, *cur; - - for (cur = outgoing; cur; cur = cur->q_next) { - if (cur->stillgoing && /* Not already done */ - !cur->chan && /* Isn't already going */ - (!best || cur->metric < best->metric)) { /* We haven't found one yet, or it's better */ - best = cur; - } - } - - return best; -} - -/*! \brief Place a call to a queue member - * - * Once metrics have been calculated for each member, this function is used - * to place a call to the appropriate member (or members). The low-level - * channel-handling and error detection is handled in ring_entry - * - * Returns 1 if a member was called successfully, 0 otherwise - */ -static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *busies) -{ - int ret = 0; - - while (ret == 0) { - struct callattempt *best = find_best(outgoing); - if (!best) { - if (option_debug) - ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n"); - break; - } - if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) { - struct callattempt *cur; - /* Ring everyone who shares this best metric (for ringall) */ - for (cur = outgoing; cur; cur = cur->q_next) { - if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) { - if (option_debug) - ast_log(LOG_DEBUG, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric); - ret |= ring_entry(qe, cur, busies); - } - } - } else { - /* Ring just the best channel */ - if (option_debug) - ast_log(LOG_DEBUG, "Trying '%s' with metric %d\n", best->interface, best->metric); - ret = ring_entry(qe, best, busies); - } - } - - return ret; -} - -static int store_next(struct queue_ent *qe, struct callattempt *outgoing) -{ - struct callattempt *best = find_best(outgoing); - - if (best) { - /* Ring just the best channel */ - if (option_debug) - ast_log(LOG_DEBUG, "Next is '%s' with metric %d\n", best->interface, best->metric); - qe->parent->rrpos = best->metric % 1000; - } else { - /* Just increment rrpos */ - if (qe->parent->wrapped) { - /* No more channels, start over */ - qe->parent->rrpos = 0; - } else { - /* Prioritize next entry */ - qe->parent->rrpos++; - } - } - qe->parent->wrapped = 0; - - return 0; -} - -static int say_periodic_announcement(struct queue_ent *qe) -{ - int res = 0; - time_t now; - - /* Get the current time */ - time(&now); - - /* Check to see if it is time to announce */ - if ((now - qe->last_periodic_announce_time) < qe->parent->periodicannouncefrequency) - return 0; - - /* Stop the music on hold so we can play our own file */ - ast_moh_stop(qe->chan); - - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Playing periodic announcement\n"); - - /* Check to make sure we have a sound file. If not, reset to the first sound file */ - if (qe->last_periodic_announce_sound >= MAX_PERIODIC_ANNOUNCEMENTS || !strlen(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound])) { - qe->last_periodic_announce_sound = 0; - } - - /* play the announcement */ - res = play_file(qe->chan, qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]); - - if ((res > 0 && !valid_exit(qe, res)) || res < 0) - res = 0; - - /* Resume Music on Hold if the caller is going to stay in the queue */ - if (!res) - ast_moh_start(qe->chan, qe->moh, NULL); - - /* update last_periodic_announce_time */ - qe->last_periodic_announce_time = now; - - /* Update the current periodic announcement to the next announcement */ - qe->last_periodic_announce_sound++; - - return res; -} - -static void record_abandoned(struct queue_ent *qe) -{ - ast_mutex_lock(&qe->parent->lock); - manager_event(EVENT_FLAG_AGENT, "QueueCallerAbandon", - "Queue: %s\r\n" - "Uniqueid: %s\r\n" - "Position: %d\r\n" - "OriginalPosition: %d\r\n" - "HoldTime: %d\r\n", - qe->parent->name, qe->chan->uniqueid, qe->pos, qe->opos, (int)(time(NULL) - qe->start)); - - qe->parent->callsabandoned++; - ast_mutex_unlock(&qe->parent->lock); -} - -/*! \brief RNA == Ring No Answer. Common code that is executed when we try a queue member and they don't answer. */ -static void rna(int rnatime, struct queue_ent *qe, char *interface, char *membername) -{ - if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", rnatime); - ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime); - if (qe->parent->autopause) { - if (!set_member_paused(qe->parent->name, interface, 1)) { - if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", interface, qe->parent->name); - } else { - if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "Failed to pause Queue Member %s in queue %s!\n", interface, qe->parent->name); - } - } - return; -} - -#define AST_MAX_WATCHERS 256 -/*! \brief Wait for a member to answer the call - * - * \param[in] qe the queue_ent corresponding to the caller in the queue - * \param[in] outgoing the list of callattempts. Relevant ones will have their chan and stillgoing parameters non-zero - * \param[in] to the amount of time (in milliseconds) to wait for a response - * \param[out] digit if a user presses a digit to exit the queue, this is the digit the caller pressed - * \param[in] prebusies number of busy members calculated prior to calling wait_for_answer - * \param[in] caller_disconnect if the 'H' option is used when calling Queue(), this is used to detect if the caller pressed * to disconnect the call - * \param[in] forwardsallowed used to detect if we should allow call forwarding, based on the 'i' option to Queue() - */ -static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect, int forwardsallowed) -{ - char *queue = qe->parent->name; - struct callattempt *o, *start = NULL, *prev = NULL; - int status; - int numbusies = prebusies; - int numnochan = 0; - int stillgoing = 0; - int orig = *to; - struct ast_frame *f; - struct callattempt *peer = NULL; - struct ast_channel *winner; - struct ast_channel *in = qe->chan; - char on[80] = ""; - char membername[80] = ""; - long starttime = 0; - long endtime = 0; - - starttime = (long) time(NULL); - - while (*to && !peer) { - int numlines, retry, pos = 1; - struct ast_channel *watchers[AST_MAX_WATCHERS]; - watchers[0] = in; - start = NULL; - - for (retry = 0; retry < 2; retry++) { - numlines = 0; - for (o = outgoing; o; o = o->q_next) { /* Keep track of important channels */ - if (o->stillgoing) { /* Keep track of important channels */ - stillgoing = 1; - if (o->chan) { - watchers[pos++] = o->chan; - if (!start) - start = o; - else - prev->call_next = o; - prev = o; - } - } - numlines++; - } - if (pos > 1 /* found */ || !stillgoing /* nobody listening */ || - (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) /* ring would not be delivered */) - break; - /* On "ringall" strategy we only move to the next penalty level - when *all* ringing phones are done in the current penalty level */ - ring_one(qe, outgoing, &numbusies); - /* and retry... */ - } - if (pos == 1 /* not found */) { - if (numlines == (numbusies + numnochan)) { - ast_log(LOG_DEBUG, "Everyone is busy at this time\n"); - } else { - ast_log(LOG_NOTICE, "No one is answering queue '%s' (%d/%d/%d)\n", queue, numlines, numbusies, numnochan); - } - *to = 0; - return NULL; - } - winner = ast_waitfor_n(watchers, pos, to); - for (o = start; o; o = o->call_next) { - if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) { - if (!peer) { - if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name); - peer = o; - } - } else if (o->chan && (o->chan == winner)) { - - ast_copy_string(on, o->member->interface, sizeof(on)); - ast_copy_string(membername, o->member->membername, sizeof(membername)); - - if (!ast_strlen_zero(o->chan->call_forward) && !forwardsallowed) { - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Forwarding %s to '%s' prevented.\n", in->name, o->chan->call_forward); - numnochan++; - do_hang(o); - winner = NULL; - continue; - } else if (!ast_strlen_zero(o->chan->call_forward)) { - char tmpchan[256]; - char *stuff; - char *tech; - - ast_copy_string(tmpchan, o->chan->call_forward, sizeof(tmpchan)); - if ((stuff = strchr(tmpchan, '/'))) { - *stuff++ = '\0'; - tech = tmpchan; - } else { - snprintf(tmpchan, sizeof(tmpchan), "%s@%s", o->chan->call_forward, o->chan->context); - stuff = tmpchan; - tech = "Local"; - } - /* Before processing channel, go ahead and check for forwarding */ - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, o->chan->name); - /* Setup parameters */ - o->chan = ast_request(tech, in->nativeformats, stuff, &status); - if (!o->chan) { - ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s'\n", tech, stuff); - o->stillgoing = 0; - numnochan++; - } else { - ast_channel_inherit_variables(in, o->chan); - ast_channel_datastore_inherit(in, o->chan); - if (o->chan->cid.cid_num) - free(o->chan->cid.cid_num); - o->chan->cid.cid_num = ast_strdup(in->cid.cid_num); - - if (o->chan->cid.cid_name) - free(o->chan->cid.cid_name); - o->chan->cid.cid_name = ast_strdup(in->cid.cid_name); - - ast_string_field_set(o->chan, accountcode, in->accountcode); - o->chan->cdrflags = in->cdrflags; - - if (in->cid.cid_ani) { - if (o->chan->cid.cid_ani) - free(o->chan->cid.cid_ani); - o->chan->cid.cid_ani = ast_strdup(in->cid.cid_ani); - } - if (o->chan->cid.cid_rdnis) - free(o->chan->cid.cid_rdnis); - o->chan->cid.cid_rdnis = ast_strdup(S_OR(in->macroexten, in->exten)); - if (ast_call(o->chan, tmpchan, 0)) { - ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan); - do_hang(o); - numnochan++; - } - } - /* Hangup the original channel now, in case we needed it */ - ast_hangup(winner); - continue; - } - f = ast_read(winner); - if (f) { - if (f->frametype == AST_FRAME_CONTROL) { - switch (f->subclass) { - case AST_CONTROL_ANSWER: - /* This is our guy if someone answered. */ - if (!peer) { - if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name); - peer = o; - } - break; - case AST_CONTROL_BUSY: - if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name); - if (in->cdr) - ast_cdr_busy(in->cdr); - do_hang(o); - endtime = (long)time(NULL); - endtime -= starttime; - rna(endtime*1000, qe, on, membername); - if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { - if (qe->parent->timeoutrestart) - *to = orig; - ring_one(qe, outgoing, &numbusies); - } - numbusies++; - break; - case AST_CONTROL_CONGESTION: - if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name); - if (in->cdr) - ast_cdr_busy(in->cdr); - endtime = (long)time(NULL); - endtime -= starttime; - rna(endtime*1000, qe, on, membername); - do_hang(o); - if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { - if (qe->parent->timeoutrestart) - *to = orig; - ring_one(qe, outgoing, &numbusies); - } - numbusies++; - break; - case AST_CONTROL_RINGING: - if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name); - break; - case AST_CONTROL_OFFHOOK: - /* Ignore going off hook */ - break; - default: - ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass); - } - } - ast_frfree(f); - } else { - endtime = (long) time(NULL) - starttime; - rna(endtime * 1000, qe, on, membername); - do_hang(o); - if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { - if (qe->parent->timeoutrestart) - *to = orig; - ring_one(qe, outgoing, &numbusies); - } - } - } - } - if (winner == in) { - f = ast_read(in); - if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) { - /* Got hung up */ - *to = -1; - if (f) - ast_frfree(f); - return NULL; - } - if ((f->frametype == AST_FRAME_DTMF) && caller_disconnect && (f->subclass == '*')) { - if (option_verbose > 3) - ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass); - *to = 0; - ast_frfree(f); - return NULL; - } - if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass)) { - if (option_verbose > 3) - ast_verbose(VERBOSE_PREFIX_3 "User pressed digit: %c\n", f->subclass); - *to = 0; - *digit = f->subclass; - ast_frfree(f); - return NULL; - } - ast_frfree(f); - } - if (!*to) { - for (o = start; o; o = o->call_next) - rna(orig, qe, o->interface, o->member->membername); - } - } - - return peer; -} -/*! \brief Check if we should start attempting to call queue members - * - * The behavior of this function is dependent first on whether autofill is enabled - * and second on whether the ring strategy is ringall. If autofill is not enabled, - * then return true if we're the head of the queue. If autofill is enabled, then - * we count the available members and see if the number of available members is enough - * that given our position in the queue, we would theoretically be able to connect to - * one of those available members - */ -static int is_our_turn(struct queue_ent *qe) -{ - struct queue_ent *ch; - struct member *cur; - int avl = 0; - int idx = 0; - int res; - - if (!qe->parent->autofill) { - /* Atomically read the parent head -- does not need a lock */ - ch = qe->parent->head; - /* If we are now at the top of the head, break out */ - if (ch == qe) { - if (option_debug) - ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name); - res = 1; - } else { - if (option_debug) - ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name); - res = 0; - } - - } else { - /* This needs a lock. How many members are available to be served? */ - ast_mutex_lock(&qe->parent->lock); - - ch = qe->parent->head; - - if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) { - if (option_debug) - ast_log(LOG_DEBUG, "Even though there may be multiple members available, the strategy is ringall so only the head call is allowed in\n"); - avl = 1; - } else { - struct ao2_iterator mem_iter = ao2_iterator_init(qe->parent->members, 0); - while ((cur = ao2_iterator_next(&mem_iter))) { - switch (cur->status) { - case AST_DEVICE_INUSE: - if (!qe->parent->ringinuse) - break; - /* else fall through */ - case AST_DEVICE_NOT_INUSE: - case AST_DEVICE_UNKNOWN: - if (!cur->paused) - avl++; - break; - } - ao2_ref(cur, -1); - } - } - - if (option_debug) - ast_log(LOG_DEBUG, "There are %d available members.\n", avl); - - while ((idx < avl) && (ch) && (ch != qe)) { - if (!ch->pending) - idx++; - ch = ch->next; - } - - /* If the queue entry is within avl [the number of available members] calls from the top ... */ - if (ch && idx < avl) { - if (option_debug) - ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name); - res = 1; - } else { - if (option_debug) - ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name); - res = 0; - } - - ast_mutex_unlock(&qe->parent->lock); - } - - return res; -} -/*! \brief The waiting areas for callers who are not actively calling members - * - * This function is one large loop. This function will return if a caller - * either exits the queue or it becomes that caller's turn to attempt calling - * queue members. Inside the loop, we service the caller with periodic announcements, - * holdtime announcements, etc. as configured in queues.conf - * - * \retval 0 if the caller's turn has arrived - * \retval -1 if the caller should exit the queue. - */ -static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *reason) -{ - int res = 0; - - /* This is the holding pen for callers 2 through maxlen */ - for (;;) { - enum queue_member_status stat; - - if (is_our_turn(qe)) - break; - - /* If we have timed out, break out */ - if (qe->expire && (time(NULL) >= qe->expire)) { - *reason = QUEUE_TIMEOUT; - break; - } - - stat = get_member_status(qe->parent, qe->max_penalty); - - /* leave the queue if no agents, if enabled */ - if (qe->parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) { - *reason = QUEUE_LEAVEEMPTY; - ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start); - leave_queue(qe); - break; - } - - /* leave the queue if no reachable agents, if enabled */ - if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) { - *reason = QUEUE_LEAVEUNAVAIL; - ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start); - leave_queue(qe); - break; - } - - /* Make a position announcement, if enabled */ - if (qe->parent->announcefrequency && !ringing && - (res = say_position(qe))) - break; - - /* If we have timed out, break out */ - if (qe->expire && (time(NULL) >= qe->expire)) { - *reason = QUEUE_TIMEOUT; - break; - } - - /* Make a periodic announcement, if enabled */ - if (qe->parent->periodicannouncefrequency && !ringing && - (res = say_periodic_announcement(qe))) - break; - - /* If we have timed out, break out */ - if (qe->expire && (time(NULL) >= qe->expire)) { - *reason = QUEUE_TIMEOUT; - break; - } - - /* Wait a second before checking again */ - if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) { - if (res > 0 && !valid_exit(qe, res)) - res = 0; - else - break; - } - - /* If we have timed out, break out */ - if (qe->expire && (time(NULL) >= qe->expire)) { - *reason = QUEUE_TIMEOUT; - break; - } - } - - return res; -} - -static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl) -{ - ast_mutex_lock(&q->lock); - time(&member->lastcall); - member->calls++; - q->callscompleted++; - if (callcompletedinsl) - q->callscompletedinsl++; - ast_mutex_unlock(&q->lock); - return 0; -} - -/*! \brief Calculate the metric of each member in the outgoing callattempts - * - * A numeric metric is given to each member depending on the ring strategy used - * by the queue. Members with lower metrics will be called before members with - * higher metrics - */ -static int calc_metric(struct call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct callattempt *tmp) -{ - if (qe->max_penalty && (mem->penalty > qe->max_penalty)) - return -1; - - switch (q->strategy) { - case QUEUE_STRATEGY_RINGALL: - /* Everyone equal, except for penalty */ - tmp->metric = mem->penalty * 1000000; - break; - case QUEUE_STRATEGY_ROUNDROBIN: - if (!pos) { - if (!q->wrapped) { - /* No more channels, start over */ - q->rrpos = 0; - } else { - /* Prioritize next entry */ - q->rrpos++; - } - q->wrapped = 0; - } - /* Fall through */ - case QUEUE_STRATEGY_RRMEMORY: - if (pos < q->rrpos) { - tmp->metric = 1000 + pos; - } else { - if (pos > q->rrpos) - /* Indicate there is another priority */ - q->wrapped = 1; - tmp->metric = pos; - } - tmp->metric += mem->penalty * 1000000; - break; - case QUEUE_STRATEGY_RANDOM: - tmp->metric = ast_random() % 1000; - tmp->metric += mem->penalty * 1000000; - break; - case QUEUE_STRATEGY_FEWESTCALLS: - tmp->metric = mem->calls; - tmp->metric += mem->penalty * 1000000; - break; - case QUEUE_STRATEGY_LEASTRECENT: - if (!mem->lastcall) - tmp->metric = 0; - else - tmp->metric = 1000000 - (time(NULL) - mem->lastcall); - tmp->metric += mem->penalty * 1000000; - break; - default: - ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy); - break; - } - return 0; -} - -struct queue_transfer_ds { - struct queue_ent *qe; - struct member *member; - time_t starttime; - int callcompletedinsl; -}; - -static void queue_transfer_destroy(void *data) -{ - struct queue_transfer_ds *qtds = data; - ast_free(qtds); -} - -/*! \brief a datastore used to help correctly log attended transfers of queue callers - */ -static const struct ast_datastore_info queue_transfer_info = { - .type = "queue_transfer", - .chan_fixup = queue_transfer_fixup, - .destroy = queue_transfer_destroy, -}; - -/*! \brief Log an attended transfer when a queue caller channel is masqueraded - * - * When a caller is masqueraded, we want to log a transfer. Fixup time is the closest we can come to when - * the actual transfer occurs. This happens during the masquerade after datastores are moved from old_chan - * to new_chan. This is why new_chan is referenced for exten, context, and datastore information. - * - * At the end of this, we want to remove the datastore so that this fixup function is not called on any - * future masquerades of the caller during the current call. - */ -static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) -{ - struct queue_transfer_ds *qtds = data; - struct queue_ent *qe = qtds->qe; - struct member *member = qtds->member; - time_t callstart = qtds->starttime; - int callcompletedinsl = qtds->callcompletedinsl; - struct ast_datastore *datastore; - - ast_queue_log(qe->parent->name, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld", - new_chan->exten, new_chan->context, (long) (callstart - qe->start), - (long) (time(NULL) - callstart)); - - update_queue(qe->parent, member, callcompletedinsl); - - /* No need to lock the channels because they are already locked in ast_do_masquerade */ - if ((datastore = ast_channel_datastore_find(old_chan, &queue_transfer_info, NULL))) { - ast_channel_datastore_remove(old_chan, datastore); - } else { - ast_log(LOG_WARNING, "Can't find the queue_transfer datastore.\n"); - } -} - -/*! \brief mechanism to tell if a queue caller was atxferred by a queue member. - * - * When a caller is atxferred, then the queue_transfer_info datastore - * is removed from the channel. If it's still there after the bridge is - * broken, then the caller was not atxferred. - * - * \note Only call this with chan locked - */ -static int attended_transfer_occurred(struct ast_channel *chan) -{ - return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1; -} - -/*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log - */ -static struct ast_datastore *setup_transfer_datastore(struct queue_ent *qe, struct member *member, time_t starttime, int callcompletedinsl) -{ - struct ast_datastore *ds; - struct queue_transfer_ds *qtds = ast_calloc(1, sizeof(*qtds)); - - if (!qtds) { - ast_log(LOG_WARNING, "Memory allocation error!\n"); - return NULL; - } - - ast_channel_lock(qe->chan); - if (!(ds = ast_channel_datastore_alloc(&queue_transfer_info, NULL))) { - ast_channel_unlock(qe->chan); - ast_log(LOG_WARNING, "Unable to create transfer datastore. queue_log will not show attended transfer\n"); - return NULL; - } - - qtds->qe = qe; - /* This member is refcounted in try_calling, so no need to add it here, too */ - qtds->member = member; - qtds->starttime = starttime; - qtds->callcompletedinsl = callcompletedinsl; - ds->data = qtds; - ast_channel_datastore_add(qe->chan, ds); - ast_channel_unlock(qe->chan); - return ds; -} - - -/*! \brief A large function which calls members, updates statistics, and bridges the caller and a member - * - * Here is the process of this function - * 1. Process any options passed to the Queue() application. Options here mean the third argument to Queue() - * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member. During this - * iteration, we also check the dialed_interfaces datastore to see if we have already attempted calling this - * member. If we have, we do not create a callattempt. This is in place to prevent call forwarding loops. Also - * during each iteration, we call calc_metric to determine which members should be rung when. - * 3. Call ring_one to place a call to the appropriate member(s) - * 4. Call wait_for_answer to wait for an answer. If no one answers, return. - * 5. Take care of any holdtime announcements, member delays, or other options which occur after a call has been answered. - * 6. Start the monitor or mixmonitor if the option is set - * 7. Remove the caller from the queue to allow other callers to advance - * 8. Bridge the call. - * 9. Do any post processing after the call has disconnected. - * - * \param[in] qe the queue_ent structure which corresponds to the caller attempting to reach members - * \param[in] options the options passed as the third parameter to the Queue() application - * \param[in] url the url passed as the fourth parameter to the Queue() application - * \param[in,out] tries the number of times we have tried calling queue members - * \param[out] noption set if the call to Queue() has the 'n' option set. - * \param[in] agi the agi passed as the fifth parameter to the Queue() application - */ - -static int try_calling(struct queue_ent *qe, const char *options, char *announceoverride, const char *url, int *tries, int *noption, const char *agi) -{ - struct member *cur; - struct callattempt *outgoing = NULL; /* the list of calls we are building */ - int to; - char oldexten[AST_MAX_EXTENSION]=""; - char oldcontext[AST_MAX_CONTEXT]=""; - char queuename[256]=""; - struct ast_channel *peer; - struct ast_channel *which; - struct callattempt *lpeer; - struct member *member; - struct ast_app *app; - int res = 0, bridge = 0; - int numbusies = 0; - int x=0; - char *announce = NULL; - char digit = 0; - time_t callstart; - time_t now = time(NULL); - struct ast_bridge_config bridge_config; - char nondataquality = 1; - char *agiexec = NULL; - int ret = 0; - const char *monitorfilename; - const char *monitor_exec; - const char *monitor_options; - char tmpid[256], tmpid2[256]; - char meid[1024], meid2[1024]; - char mixmonargs[1512]; - struct ast_app *mixmonapp = NULL; - char *p; - char vars[2048]; - int forwardsallowed = 1; - int callcompletedinsl; - struct ao2_iterator memi; - struct ast_datastore *datastore, *transfer_ds; - - ast_channel_lock(qe->chan); - datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL); - ast_channel_unlock(qe->chan); - - memset(&bridge_config, 0, sizeof(bridge_config)); - time(&now); - - /* If we've already exceeded our timeout, then just stop - * This should be extremely rare. queue_exec will take care - * of removing the caller and reporting the timeout as the reason. - */ - if (qe->expire && now >= qe->expire) { - res = 0; - goto out; - } - - for (; options && *options; options++) - switch (*options) { - case 't': - ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_REDIRECT); - break; - case 'T': - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_REDIRECT); - break; - case 'w': - ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMON); - break; - case 'W': - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON); - break; - case 'd': - nondataquality = 0; - break; - case 'h': - ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_DISCONNECT); - break; - case 'H': - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT); - break; - case 'n': - if (qe->parent->strategy == QUEUE_STRATEGY_ROUNDROBIN || qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) - (*tries)++; - else - *tries = qe->parent->membercount; - *noption = 1; - break; - case 'i': - forwardsallowed = 0; - break; - } - - /* Hold the lock while we setup the outgoing calls */ - if (use_weight) - AST_LIST_LOCK(&queues); - ast_mutex_lock(&qe->parent->lock); - if (option_debug) - ast_log(LOG_DEBUG, "%s is trying to call a queue member.\n", - qe->chan->name); - ast_copy_string(queuename, qe->parent->name, sizeof(queuename)); - if (!ast_strlen_zero(qe->announce)) - announce = qe->announce; - if (!ast_strlen_zero(announceoverride)) - announce = announceoverride; - - memi = ao2_iterator_init(qe->parent->members, 0); - while ((cur = ao2_iterator_next(&memi))) { - struct callattempt *tmp = ast_calloc(1, sizeof(*tmp)); - struct ast_dialed_interface *di; - AST_LIST_HEAD(, ast_dialed_interface) *dialed_interfaces; - if (!tmp) { - ao2_ref(cur, -1); - ast_mutex_unlock(&qe->parent->lock); - if (use_weight) - AST_LIST_UNLOCK(&queues); - goto out; - } - if (!datastore) { - if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) { - ao2_ref(cur, -1); - ast_mutex_unlock(&qe->parent->lock); - if (use_weight) - AST_LIST_UNLOCK(&queues); - free(tmp); - goto out; - } - datastore->inheritance = DATASTORE_INHERIT_FOREVER; - if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) { - ao2_ref(cur, -1); - ast_mutex_unlock(&qe->parent->lock); - if (use_weight) - AST_LIST_UNLOCK(&queues); - free(tmp); - goto out; - } - datastore->data = dialed_interfaces; - AST_LIST_HEAD_INIT(dialed_interfaces); - - ast_channel_lock(qe->chan); - ast_channel_datastore_add(qe->chan, datastore); - ast_channel_unlock(qe->chan); - } else - dialed_interfaces = datastore->data; - - AST_LIST_LOCK(dialed_interfaces); - AST_LIST_TRAVERSE(dialed_interfaces, di, list) { - if (!strcasecmp(cur->interface, di->interface)) { - ast_log(LOG_DEBUG, "Skipping dialing interface '%s' since it has already been dialed\n", - di->interface); - break; - } - } - AST_LIST_UNLOCK(dialed_interfaces); - - if (di) { - free(tmp); - continue; - } - - /* It is always ok to dial a Local interface. We only keep track of - * which "real" interfaces have been dialed. The Local channel will - * inherit this list so that if it ends up dialing a real interface, - * it won't call one that has already been called. */ - if (strncasecmp(cur->interface, "Local/", 6)) { - if (!(di = ast_calloc(1, sizeof(*di) + strlen(cur->interface)))) { - ao2_ref(cur, -1); - ast_mutex_unlock(&qe->parent->lock); - if (use_weight) - AST_LIST_UNLOCK(&queues); - free(tmp); - goto out; - } - strcpy(di->interface, cur->interface); - - AST_LIST_LOCK(dialed_interfaces); - AST_LIST_INSERT_TAIL(dialed_interfaces, di, list); - AST_LIST_UNLOCK(dialed_interfaces); - } - - tmp->stillgoing = -1; - tmp->member = cur; - tmp->oldstatus = cur->status; - tmp->lastcall = cur->lastcall; - ast_copy_string(tmp->interface, cur->interface, sizeof(tmp->interface)); - /* Special case: If we ring everyone, go ahead and ring them, otherwise - just calculate their metric for the appropriate strategy */ - if (!calc_metric(qe->parent, cur, x++, qe, tmp)) { - /* Put them in the list of outgoing thingies... We're ready now. - XXX If we're forcibly removed, these outgoing calls won't get - hung up XXX */ - tmp->q_next = outgoing; - outgoing = tmp; - /* If this line is up, don't try anybody else */ - if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP)) - break; - } else { - ao2_ref(cur, -1); - free(tmp); - } - } - if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout)) - to = (qe->expire - now) * 1000; - else - to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1; - ++qe->pending; - ast_mutex_unlock(&qe->parent->lock); - ring_one(qe, outgoing, &numbusies); - if (use_weight) - AST_LIST_UNLOCK(&queues); - lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed); - /* The ast_channel_datastore_remove() function could fail here if the - * datastore was moved to another channel during a masquerade. If this is - * the case, don't free the datastore here because later, when the channel - * to which the datastore was moved hangs up, it will attempt to free this - * datastore again, causing a crash - */ - ast_channel_lock(qe->chan); - if (datastore && !ast_channel_datastore_remove(qe->chan, datastore)) { - ast_channel_datastore_free(datastore); - } - ast_channel_unlock(qe->chan); - ast_mutex_lock(&qe->parent->lock); - if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) { - store_next(qe, outgoing); - } - ast_mutex_unlock(&qe->parent->lock); - peer = lpeer ? lpeer->chan : NULL; - if (!peer) { - qe->pending = 0; - if (to) { - /* Must gotten hung up */ - res = -1; - } else { - /* User exited by pressing a digit */ - res = digit; - } - if (option_debug && res == -1) - ast_log(LOG_DEBUG, "%s: Nobody answered.\n", qe->chan->name); - } else { /* peer is valid */ - /* Ah ha! Someone answered within the desired timeframe. Of course after this - we will always return with -1 so that it is hung up properly after the - conversation. */ - if (!strcmp(qe->chan->tech->type, "Zap")) - ast_channel_setoption(qe->chan, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0); - if (!strcmp(peer->tech->type, "Zap")) - ast_channel_setoption(peer, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0); - /* Update parameters for the queue */ - time(&now); - recalc_holdtime(qe, (now - qe->start)); - ast_mutex_lock(&qe->parent->lock); - callcompletedinsl = ((now - qe->start) <= qe->parent->servicelevel); - ast_mutex_unlock(&qe->parent->lock); - member = lpeer->member; - /* Increment the refcount for this member, since we're going to be using it for awhile in here. */ - ao2_ref(member, 1); - hangupcalls(outgoing, peer); - outgoing = NULL; - if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) { - int res2; - - res2 = ast_autoservice_start(qe->chan); - if (!res2) { - if (qe->parent->memberdelay) { - ast_log(LOG_NOTICE, "Delaying member connect for %d seconds\n", qe->parent->memberdelay); - res2 |= ast_safe_sleep(peer, qe->parent->memberdelay * 1000); - } - if (!res2 && announce) { - play_file(peer, announce); - } - if (!res2 && qe->parent->reportholdtime) { - if (!play_file(peer, qe->parent->sound_reporthold)) { - int holdtime; - - time(&now); - holdtime = abs((now - qe->start) / 60); - if (holdtime < 2) { - play_file(peer, qe->parent->sound_lessthan); - ast_say_number(peer, 2, AST_DIGIT_ANY, peer->language, NULL); - } else - ast_say_number(peer, holdtime, AST_DIGIT_ANY, peer->language, NULL); - play_file(peer, qe->parent->sound_minutes); - } - } - } - res2 |= ast_autoservice_stop(qe->chan); - if (peer->_softhangup) { - /* Agent must have hung up */ - ast_log(LOG_WARNING, "Agent on %s hungup on the customer.\n", peer->name); - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "AGENTDUMP", "%s", ""); - if (qe->parent->eventwhencalled) - manager_event(EVENT_FLAG_AGENT, "AgentDump", - "Queue: %s\r\n" - "Uniqueid: %s\r\n" - "Channel: %s\r\n" - "Member: %s\r\n" - "MemberName: %s\r\n" - "%s", - queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, - qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); - ast_hangup(peer); - ao2_ref(member, -1); - goto out; - } else if (res2) { - /* Caller must have hung up just before being connected*/ - ast_log(LOG_NOTICE, "Caller was about to talk to agent on %s but the caller hungup.\n", peer->name); - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "ABANDON", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start); - record_abandoned(qe); - ast_hangup(peer); - ao2_ref(member, -1); - return -1; - } - } - /* Stop music on hold */ - ast_moh_stop(qe->chan); - /* If appropriate, log that we have a destination channel */ - if (qe->chan->cdr) - ast_cdr_setdestchan(qe->chan->cdr, peer->name); - /* Make sure channels are compatible */ - res = ast_channel_make_compatible(qe->chan, peer); - if (res < 0) { - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "SYSCOMPAT", "%s", ""); - ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", qe->chan->name, peer->name); - record_abandoned(qe); - ast_hangup(peer); - ao2_ref(member, -1); - return -1; - } - - if (qe->parent->setinterfacevar) - pbx_builtin_setvar_helper(qe->chan, "MEMBERINTERFACE", member->interface); - - /* Begin Monitoring */ - if (qe->parent->monfmt && *qe->parent->monfmt) { - if (!qe->parent->montype) { - if (option_debug) - ast_log(LOG_DEBUG, "Starting Monitor as requested.\n"); - monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME"); - if (pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC") || pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC_ARGS")) - which = qe->chan; - else - which = peer; - if (monitorfilename) - ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1 ); - else if (qe->chan->cdr) - ast_monitor_start(which, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 ); - else { - /* Last ditch effort -- no CDR, make up something */ - snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); - ast_monitor_start(which, qe->parent->monfmt, tmpid, 1 ); - } - if (qe->parent->monjoin) - ast_monitor_setjoinfiles(which, 1); - } else { - if (option_debug) - ast_log(LOG_DEBUG, "Starting MixMonitor as requested.\n"); - monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME"); - if (!monitorfilename) { - if (qe->chan->cdr) - ast_copy_string(tmpid, qe->chan->cdr->uniqueid, sizeof(tmpid)-1); - else - snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); - } else { - ast_copy_string(tmpid2, monitorfilename, sizeof(tmpid2)-1); - for (p = tmpid2; *p ; p++) { - if (*p == '^' && *(p+1) == '{') { - *p = '$'; - } - } - - memset(tmpid, 0, sizeof(tmpid)); - pbx_substitute_variables_helper(qe->chan, tmpid2, tmpid, sizeof(tmpid) - 1); - } - - monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC"); - monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS"); - - if (monitor_exec) { - ast_copy_string(meid2, monitor_exec, sizeof(meid2)-1); - for (p = meid2; *p ; p++) { - if (*p == '^' && *(p+1) == '{') { - *p = '$'; - } - } - - memset(meid, 0, sizeof(meid)); - pbx_substitute_variables_helper(qe->chan, meid2, meid, sizeof(meid) - 1); - } - - snprintf(tmpid2, sizeof(tmpid2)-1, "%s.%s", tmpid, qe->parent->monfmt); - - mixmonapp = pbx_findapp("MixMonitor"); - - if (strchr(tmpid2, '|')) { - ast_log(LOG_WARNING, "monitor-format (in queues.conf) and MONITOR_FILENAME cannot contain a '|'! Not recording.\n"); - mixmonapp = NULL; - } - - if (!monitor_options) - monitor_options = ""; - - if (strchr(monitor_options, '|')) { - ast_log(LOG_WARNING, "MONITOR_OPTIONS cannot contain a '|'! Not recording.\n"); - mixmonapp = NULL; - } - - if (mixmonapp) { - if (!ast_strlen_zero(monitor_exec)) - snprintf(mixmonargs, sizeof(mixmonargs)-1, "%s|b%s|%s", tmpid2, monitor_options, monitor_exec); - else - snprintf(mixmonargs, sizeof(mixmonargs)-1, "%s|b%s", tmpid2, monitor_options); - - if (option_debug) - ast_log(LOG_DEBUG, "Arguments being passed to MixMonitor: %s\n", mixmonargs); - /* We purposely lock the CDR so that pbx_exec does not update the application data */ - if (qe->chan->cdr) - ast_set_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED); - ret = pbx_exec(qe->chan, mixmonapp, mixmonargs); - if (qe->chan->cdr) - ast_clear_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED); - - } else - ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n"); - - } - } - /* Drop out of the queue at this point, to prepare for next caller */ - leave_queue(qe); - if (!ast_strlen_zero(url) && ast_channel_supports_html(peer)) { - if (option_debug) - ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url); - ast_channel_sendurl(peer, url); - } - if (!ast_strlen_zero(agi)) { - if (option_debug) - ast_log(LOG_DEBUG, "app_queue: agi=%s.\n", agi); - app = pbx_findapp("agi"); - if (app) { - agiexec = ast_strdupa(agi); - ret = pbx_exec(qe->chan, app, agiexec); - } else - ast_log(LOG_WARNING, "Asked to execute an AGI on this channel, but could not find application (agi)!\n"); - } - qe->handled++; - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "CONNECT", "%ld|%s", (long)time(NULL) - qe->start, peer->uniqueid); - if (qe->parent->eventwhencalled) - manager_event(EVENT_FLAG_AGENT, "AgentConnect", - "Queue: %s\r\n" - "Uniqueid: %s\r\n" - "Channel: %s\r\n" - "Member: %s\r\n" - "MemberName: %s\r\n" - "Holdtime: %ld\r\n" - "BridgedChannel: %s\r\n" - "%s", - queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, - (long)time(NULL) - qe->start, peer->uniqueid, - qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); - ast_copy_string(oldcontext, qe->chan->context, sizeof(oldcontext)); - ast_copy_string(oldexten, qe->chan->exten, sizeof(oldexten)); - time(&callstart); - - if (member->status == AST_DEVICE_NOT_INUSE) - ast_log(LOG_WARNING, "The device state of this queue member, %s, is still 'Not in Use' when it probably should not be! Please check UPGRADE.txt for correct configuration settings.\n", member->membername); - - transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl); - bridge = ast_bridge_call(qe->chan,peer, &bridge_config); - - ast_channel_lock(qe->chan); - if (!attended_transfer_occurred(qe->chan)) { - struct ast_datastore *tds; - if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) { - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld", - qe->chan->exten, qe->chan->context, (long) (callstart - qe->start), - (long) (time(NULL) - callstart)); - } else if (qe->chan->_softhangup) { - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETECALLER", "%ld|%ld|%d", - (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); - if (qe->parent->eventwhencalled) - manager_event(EVENT_FLAG_AGENT, "AgentComplete", - "Queue: %s\r\n" - "Uniqueid: %s\r\n" - "Channel: %s\r\n" - "Member: %s\r\n" - "MemberName: %s\r\n" - "HoldTime: %ld\r\n" - "TalkTime: %ld\r\n" - "Reason: caller\r\n" - "%s", - queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, - (long)(callstart - qe->start), (long)(time(NULL) - callstart), - qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); - } else { - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETEAGENT", "%ld|%ld|%d", - (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); - if (qe->parent->eventwhencalled) - manager_event(EVENT_FLAG_AGENT, "AgentComplete", - "Queue: %s\r\n" - "Uniqueid: %s\r\n" - "Channel: %s\r\n" - "MemberName: %s\r\n" - "HoldTime: %ld\r\n" - "TalkTime: %ld\r\n" - "Reason: agent\r\n" - "%s", - queuename, qe->chan->uniqueid, peer->name, member->membername, (long)(callstart - qe->start), - (long)(time(NULL) - callstart), - qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); - } - if ((tds = ast_channel_datastore_find(qe->chan, &queue_transfer_info, NULL))) { - ast_channel_datastore_remove(qe->chan, tds); - } - update_queue(qe->parent, member, callcompletedinsl); - } - - if (transfer_ds) { - ast_channel_datastore_free(transfer_ds); - } - ast_channel_unlock(qe->chan); - ast_hangup(peer); - res = bridge ? bridge : 1; - ao2_ref(member, -1); - } -out: - hangupcalls(outgoing, NULL); - - return res; -} - -static int wait_a_bit(struct queue_ent *qe) -{ - /* Don't need to hold the lock while we setup the outgoing calls */ - int retrywait = qe->parent->retry * 1000; - - int res = ast_waitfordigit(qe->chan, retrywait); - if (res > 0 && !valid_exit(qe, res)) - res = 0; - - return res; -} - -static struct member *interface_exists(struct call_queue *q, const char *interface) -{ - struct member *mem; - struct ao2_iterator mem_iter; - - if (!q) - return NULL; - - mem_iter = ao2_iterator_init(q->members, 0); - while ((mem = ao2_iterator_next(&mem_iter))) { - if (!strcasecmp(interface, mem->interface)) - return mem; - ao2_ref(mem, -1); - } - - return NULL; -} - - -/* Dump all members in a specific queue to the database - * - * <pm_family>/<queuename> = <interface>;<penalty>;<paused>[|...] - * - */ -static void dump_queue_members(struct call_queue *pm_queue) -{ - struct member *cur_member; - char value[PM_MAX_LEN]; - int value_len = 0; - int res; - struct ao2_iterator mem_iter; - - memset(value, 0, sizeof(value)); - - if (!pm_queue) - return; - - mem_iter = ao2_iterator_init(pm_queue->members, 0); - while ((cur_member = ao2_iterator_next(&mem_iter))) { - if (!cur_member->dynamic) { - ao2_ref(cur_member, -1); - continue; - } - - res = snprintf(value + value_len, sizeof(value) - value_len, "%s%s;%d;%d;%s", - value_len ? "|" : "", cur_member->interface, cur_member->penalty, cur_member->paused, cur_member->membername); - - ao2_ref(cur_member, -1); - - if (res != strlen(value + value_len)) { - ast_log(LOG_WARNING, "Could not create persistent member string, out of space\n"); - break; - } - value_len += res; - } - - if (value_len && !cur_member) { - if (ast_db_put(pm_family, pm_queue->name, value)) - ast_log(LOG_WARNING, "failed to create persistent dynamic entry!\n"); - } else - /* Delete the entry if the queue is empty or there is an error */ - ast_db_del(pm_family, pm_queue->name); -} - -static int remove_from_queue(const char *queuename, const char *interface) -{ - struct call_queue *q; - struct member *mem, tmpmem; - int res = RES_NOSUCHQUEUE; - - ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); - - AST_LIST_LOCK(&queues); - AST_LIST_TRAVERSE(&queues, q, list) { - ast_mutex_lock(&q->lock); - if (strcmp(q->name, queuename)) { - ast_mutex_unlock(&q->lock); - continue; - } - - if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) { - /* XXX future changes should beware of this assumption!! */ - if (!mem->dynamic) { - res = RES_NOT_DYNAMIC; - ao2_ref(mem, -1); - ast_mutex_unlock(&q->lock); - break; - } - q->membercount--; - manager_event(EVENT_FLAG_AGENT, "QueueMemberRemoved", - "Queue: %s\r\n" - "Location: %s\r\n" - "MemberName: %s\r\n", - q->name, mem->interface, mem->membername); - ao2_unlink(q->members, mem); - ao2_ref(mem, -1); - - if (queue_persistent_members) - dump_queue_members(q); - - res = RES_OKAY; - } else { - res = RES_EXISTS; - } - ast_mutex_unlock(&q->lock); - break; - } - - if (res == RES_OKAY) - remove_from_interfaces(interface); - - AST_LIST_UNLOCK(&queues); - - return res; -} - - -static int add_to_queue(const char *queuename, const char *interface, const char *membername, int penalty, int paused, int dump) -{ - struct call_queue *q; - struct member *new_member, *old_member; - int res = RES_NOSUCHQUEUE; - - /* \note Ensure the appropriate realtime queue is loaded. Note that this - * short-circuits if the queue is already in memory. */ - if (!(q = load_realtime_queue(queuename))) - return res; - - AST_LIST_LOCK(&queues); - - ast_mutex_lock(&q->lock); - if ((old_member = interface_exists(q, interface)) == NULL) { - add_to_interfaces(interface); - if ((new_member = create_queue_member(interface, membername, penalty, paused))) { - new_member->dynamic = 1; - ao2_link(q->members, new_member); - q->membercount++; - manager_event(EVENT_FLAG_AGENT, "QueueMemberAdded", - "Queue: %s\r\n" - "Location: %s\r\n" - "MemberName: %s\r\n" - "Membership: %s\r\n" - "Penalty: %d\r\n" - "CallsTaken: %d\r\n" - "LastCall: %d\r\n" - "Status: %d\r\n" - "Paused: %d\r\n", - q->name, new_member->interface, new_member->membername, - "dynamic", - new_member->penalty, new_member->calls, (int) new_member->lastcall, - new_member->status, new_member->paused); - - ao2_ref(new_member, -1); - new_member = NULL; - - if (dump) - dump_queue_members(q); - - res = RES_OKAY; - } else { - res = RES_OUTOFMEMORY; - } - } else { - ao2_ref(old_member, -1); - res = RES_EXISTS; - } - ast_mutex_unlock(&q->lock); - AST_LIST_UNLOCK(&queues); - - return res; -} - -static int set_member_paused(const char *queuename, const char *interface, int paused) -{ - int found = 0; - struct call_queue *q; - struct member *mem; - - /* Special event for when all queues are paused - individual events still generated */ - /* XXX In all other cases, we use the membername, but since this affects all queues, we cannot */ - if (ast_strlen_zero(queuename)) - ast_queue_log("NONE", "NONE", interface, (paused ? "PAUSEALL" : "UNPAUSEALL"), "%s", ""); - - AST_LIST_LOCK(&queues); - AST_LIST_TRAVERSE(&queues, q, list) { - ast_mutex_lock(&q->lock); - if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) { - if ((mem = interface_exists(q, interface))) { - found++; - if (mem->paused == paused) - ast_log(LOG_DEBUG, "%spausing already-%spaused queue member %s:%s\n", (paused ? "" : "un"), (paused ? "" : "un"), q->name, interface); - mem->paused = paused; - - if (queue_persistent_members) - dump_queue_members(q); - - if (mem->realtime) - update_realtime_member_field(mem, q->name, "paused", paused ? "1" : "0"); - - ast_queue_log(q->name, "NONE", mem->membername, (paused ? "PAUSE" : "UNPAUSE"), "%s", ""); - - manager_event(EVENT_FLAG_AGENT, "QueueMemberPaused", - "Queue: %s\r\n" - "Location: %s\r\n" - "MemberName: %s\r\n" - "Paused: %d\r\n", - q->name, mem->interface, mem->membername, paused); - ao2_ref(mem, -1); - } - } - ast_mutex_unlock(&q->lock); - } - AST_LIST_UNLOCK(&queues); - - return found ? RESULT_SUCCESS : RESULT_FAILURE; -} - -/* Reload dynamic queue members persisted into the astdb */ -static void reload_queue_members(void) -{ - char *cur_ptr; - char *queue_name; - char *member; - char *interface; - char *membername = NULL; - char *penalty_tok; - int penalty = 0; - char *paused_tok; - int paused = 0; - struct ast_db_entry *db_tree; - struct ast_db_entry *entry; - struct call_queue *cur_queue; - char queue_data[PM_MAX_LEN]; - - AST_LIST_LOCK(&queues); - - /* Each key in 'pm_family' is the name of a queue */ - db_tree = ast_db_gettree(pm_family, NULL); - for (entry = db_tree; entry; entry = entry->next) { - - queue_name = entry->key + strlen(pm_family) + 2; - - AST_LIST_TRAVERSE(&queues, cur_queue, list) { - ast_mutex_lock(&cur_queue->lock); - if (!strcmp(queue_name, cur_queue->name)) - break; - ast_mutex_unlock(&cur_queue->lock); - } - - if (!cur_queue) - cur_queue = load_realtime_queue(queue_name); - - if (!cur_queue) { - /* If the queue no longer exists, remove it from the - * database */ - ast_log(LOG_WARNING, "Error loading persistent queue: '%s': it does not exist\n", queue_name); - ast_db_del(pm_family, queue_name); - continue; - } else - ast_mutex_unlock(&cur_queue->lock); - - if (ast_db_get(pm_family, queue_name, queue_data, PM_MAX_LEN)) - continue; - - cur_ptr = queue_data; - while ((member = strsep(&cur_ptr, "|"))) { - if (ast_strlen_zero(member)) - continue; - - interface = strsep(&member, ";"); - penalty_tok = strsep(&member, ";"); - paused_tok = strsep(&member, ";"); - membername = strsep(&member, ";"); - - if (!penalty_tok) { - ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (penalty)\n", queue_name); - break; - } - penalty = strtol(penalty_tok, NULL, 10); - if (errno == ERANGE) { - ast_log(LOG_WARNING, "Error converting penalty: %s: Out of range.\n", penalty_tok); - break; - } - - if (!paused_tok) { - ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (paused)\n", queue_name); - break; - } - paused = strtol(paused_tok, NULL, 10); - if ((errno == ERANGE) || paused < 0 || paused > 1) { - ast_log(LOG_WARNING, "Error converting paused: %s: Expected 0 or 1.\n", paused_tok); - break; - } - if (ast_strlen_zero(membername)) - membername = interface; - - if (option_debug) - ast_log(LOG_DEBUG, "Reload Members: Queue: %s Member: %s Name: %s Penalty: %d Paused: %d\n", queue_name, interface, membername, penalty, paused); - - if (add_to_queue(queue_name, interface, membername, penalty, paused, 0) == RES_OUTOFMEMORY) { - ast_log(LOG_ERROR, "Out of Memory when reloading persistent queue member\n"); - break; - } - } - } - - AST_LIST_UNLOCK(&queues); - if (db_tree) { - ast_log(LOG_NOTICE, "Queue members successfully reloaded from database.\n"); - ast_db_freetree(db_tree); - } -} - -static int pqm_exec(struct ast_channel *chan, void *data) -{ - struct ast_module_user *lu; - char *parse; - int priority_jump = 0; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(queuename); - AST_APP_ARG(interface); - AST_APP_ARG(options); - ); - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "PauseQueueMember requires an argument ([queuename]|interface[|options])\n"); - return -1; - } - - parse = ast_strdupa(data); - - AST_STANDARD_APP_ARGS(args, parse); - - lu = ast_module_user_add(chan); - - if (args.options) { - if (strchr(args.options, 'j')) - priority_jump = 1; - } - - if (ast_strlen_zero(args.interface)) { - ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n"); - ast_module_user_remove(lu); - return -1; - } - - if (set_member_paused(args.queuename, args.interface, 1)) { - ast_log(LOG_WARNING, "Attempt to pause interface %s, not found\n", args.interface); - if (priority_jump || ast_opt_priority_jumping) { - if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) { - pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND"); - ast_module_user_remove(lu); - return 0; - } - } - ast_module_user_remove(lu); - pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND"); - return 0; - } - - ast_module_user_remove(lu); - pbx_builtin_setvar_helper(chan, "PQMSTATUS", "PAUSED"); - - return 0; -} - -static int upqm_exec(struct ast_channel *chan, void *data) -{ - struct ast_module_user *lu; - char *parse; - int priority_jump = 0; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(queuename); - AST_APP_ARG(interface); - AST_APP_ARG(options); - ); - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "UnpauseQueueMember requires an argument ([queuename]|interface[|options])\n"); - return -1; - } - - parse = ast_strdupa(data); - - AST_STANDARD_APP_ARGS(args, parse); - - lu = ast_module_user_add(chan); - - if (args.options) { - if (strchr(args.options, 'j')) - priority_jump = 1; - } - - if (ast_strlen_zero(args.interface)) { - ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n"); - ast_module_user_remove(lu); - return -1; - } - - if (set_member_paused(args.queuename, args.interface, 0)) { - ast_log(LOG_WARNING, "Attempt to unpause interface %s, not found\n", args.interface); - if (priority_jump || ast_opt_priority_jumping) { - if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) { - pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND"); - ast_module_user_remove(lu); - return 0; - } - } - ast_module_user_remove(lu); - pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND"); - return 0; - } - - ast_module_user_remove(lu); - pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "UNPAUSED"); - - return 0; -} - -static int rqm_exec(struct ast_channel *chan, void *data) -{ - int res=-1; - struct ast_module_user *lu; - char *parse, *temppos = NULL; - int priority_jump = 0; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(queuename); - AST_APP_ARG(interface); - AST_APP_ARG(options); - ); - - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename[|interface[|options]])\n"); - return -1; - } - - parse = ast_strdupa(data); - - AST_STANDARD_APP_ARGS(args, parse); - - lu = ast_module_user_add(chan); - - if (ast_strlen_zero(args.interface)) { - args.interface = ast_strdupa(chan->name); - temppos = strrchr(args.interface, '-'); - if (temppos) - *temppos = '\0'; - } - - if (args.options) { - if (strchr(args.options, 'j')) - priority_jump = 1; - } - - switch (remove_from_queue(args.queuename, args.interface)) { - case RES_OKAY: - ast_queue_log(args.queuename, chan->uniqueid, args.interface, "REMOVEMEMBER", "%s", ""); - ast_log(LOG_NOTICE, "Removed interface '%s' from queue '%s'\n", args.interface, args.queuename); - pbx_builtin_setvar_helper(chan, "RQMSTATUS", "REMOVED"); - res = 0; - break; - case RES_EXISTS: - ast_log(LOG_DEBUG, "Unable to remove interface '%s' from queue '%s': Not there\n", args.interface, args.queuename); - if (priority_jump || ast_opt_priority_jumping) - ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); - pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTINQUEUE"); - res = 0; - break; - case RES_NOSUCHQUEUE: - ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", args.queuename); - pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOSUCHQUEUE"); - res = 0; - break; - case RES_NOT_DYNAMIC: - ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': '%s' is not a dynamic member\n", args.queuename, args.interface); - pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTDYNAMIC"); - res = 0; - break; - } - - ast_module_user_remove(lu); - - return res; -} - -static int aqm_exec(struct ast_channel *chan, void *data) -{ - int res=-1; - struct ast_module_user *lu; - char *parse, *temppos = NULL; - int priority_jump = 0; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(queuename); - AST_APP_ARG(interface); - AST_APP_ARG(penalty); - AST_APP_ARG(options); - AST_APP_ARG(membername); - ); - int penalty = 0; - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[|[interface]|[penalty][|options][|membername]])\n"); - return -1; - } - - parse = ast_strdupa(data); - - AST_STANDARD_APP_ARGS(args, parse); - - lu = ast_module_user_add(chan); - - if (ast_strlen_zero(args.interface)) { - args.interface = ast_strdupa(chan->name); - temppos = strrchr(args.interface, '-'); - if (temppos) - *temppos = '\0'; - } - - if (!ast_strlen_zero(args.penalty)) { - if ((sscanf(args.penalty, "%d", &penalty) != 1) || penalty < 0) { - ast_log(LOG_WARNING, "Penalty '%s' is invalid, must be an integer >= 0\n", args.penalty); - penalty = 0; - } - } - - if (args.options) { - if (strchr(args.options, 'j')) - priority_jump = 1; - } - - switch (add_to_queue(args.queuename, args.interface, args.membername, penalty, 0, queue_persistent_members)) { - case RES_OKAY: - ast_queue_log(args.queuename, chan->uniqueid, args.interface, "ADDMEMBER", "%s", ""); - ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", args.interface, args.queuename); - pbx_builtin_setvar_helper(chan, "AQMSTATUS", "ADDED"); - res = 0; - break; - case RES_EXISTS: - ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': Already there\n", args.interface, args.queuename); - if (priority_jump || ast_opt_priority_jumping) - ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); - pbx_builtin_setvar_helper(chan, "AQMSTATUS", "MEMBERALREADY"); - res = 0; - break; - case RES_NOSUCHQUEUE: - ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", args.queuename); - pbx_builtin_setvar_helper(chan, "AQMSTATUS", "NOSUCHQUEUE"); - res = 0; - break; - case RES_OUTOFMEMORY: - ast_log(LOG_ERROR, "Out of memory adding member %s to queue %s\n", args.interface, args.queuename); - break; - } - - ast_module_user_remove(lu); - - return res; -} - -static int ql_exec(struct ast_channel *chan, void *data) -{ - struct ast_module_user *u; - char *parse; - - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(queuename); - AST_APP_ARG(uniqueid); - AST_APP_ARG(membername); - AST_APP_ARG(event); - AST_APP_ARG(params); - ); - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo]\n"); - return -1; - } - - u = ast_module_user_add(chan); - - parse = ast_strdupa(data); - - AST_STANDARD_APP_ARGS(args, parse); - - if (ast_strlen_zero(args.queuename) || ast_strlen_zero(args.uniqueid) - || ast_strlen_zero(args.membername) || ast_strlen_zero(args.event)) { - ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo])\n"); - ast_module_user_remove(u); - return -1; - } - - ast_queue_log(args.queuename, args.uniqueid, args.membername, args.event, - "%s", args.params ? args.params : ""); - - ast_module_user_remove(u); - - return 0; -} - -/*!\brief The starting point for all queue calls - * - * The process involved here is to - * 1. Parse the options specified in the call to Queue() - * 2. Join the queue - * 3. Wait in a loop until it is our turn to try calling a queue member - * 4. Attempt to call a queue member - * 5. If 4. did not result in a bridged call, then check for between - * call options such as periodic announcements etc. - * 6. Try 4 again uless some condition (such as an expiration time) causes us to - * exit the queue. - */ -static int queue_exec(struct ast_channel *chan, void *data) -{ - int res=-1; - int ringing=0; - struct ast_module_user *lu; - const char *user_priority; - const char *max_penalty_str; - int prio; - int max_penalty; - enum queue_result reason = QUEUE_UNKNOWN; - /* whether to exit Queue application after the timeout hits */ - int tries = 0; - int noption = 0; - char *parse; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(queuename); - AST_APP_ARG(options); - AST_APP_ARG(url); - AST_APP_ARG(announceoverride); - AST_APP_ARG(queuetimeoutstr); - AST_APP_ARG(agi); - ); - /* Our queue entry */ - struct queue_ent qe; - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "Queue requires an argument: queuename[|options[|URL[|announceoverride[|timeout[|agi]]]]]\n"); - return -1; - } - - parse = ast_strdupa(data); - AST_STANDARD_APP_ARGS(args, parse); - - lu = ast_module_user_add(chan); - - /* Setup our queue entry */ - memset(&qe, 0, sizeof(qe)); - qe.start = time(NULL); - - /* set the expire time based on the supplied timeout; */ - if (!ast_strlen_zero(args.queuetimeoutstr)) - qe.expire = qe.start + atoi(args.queuetimeoutstr); - else - qe.expire = 0; - - /* Get the priority from the variable ${QUEUE_PRIO} */ - user_priority = pbx_builtin_getvar_helper(chan, "QUEUE_PRIO"); - if (user_priority) { - if (sscanf(user_priority, "%d", &prio) == 1) { - if (option_debug) - ast_log(LOG_DEBUG, "%s: Got priority %d from ${QUEUE_PRIO}.\n", - chan->name, prio); - } else { - ast_log(LOG_WARNING, "${QUEUE_PRIO}: Invalid value (%s), channel %s.\n", - user_priority, chan->name); - prio = 0; - } - } else { - if (option_debug > 2) - ast_log(LOG_DEBUG, "NO QUEUE_PRIO variable found. Using default.\n"); - prio = 0; - } - - /* Get the maximum penalty from the variable ${QUEUE_MAX_PENALTY} */ - if ((max_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MAX_PENALTY"))) { - if (sscanf(max_penalty_str, "%d", &max_penalty) == 1) { - if (option_debug) - ast_log(LOG_DEBUG, "%s: Got max penalty %d from ${QUEUE_MAX_PENALTY}.\n", - chan->name, max_penalty); - } else { - ast_log(LOG_WARNING, "${QUEUE_MAX_PENALTY}: Invalid value (%s), channel %s.\n", - max_penalty_str, chan->name); - max_penalty = 0; - } - } else { - max_penalty = 0; - } - - if (args.options && (strchr(args.options, 'r'))) - ringing = 1; - - if (option_debug) - ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s, expires: %ld, priority: %d\n", - args.queuename, args.options, args.url, args.announceoverride, (long)qe.expire, prio); - - qe.chan = chan; - qe.prio = prio; - qe.max_penalty = max_penalty; - qe.last_pos_said = 0; - qe.last_pos = 0; - qe.last_periodic_announce_time = time(NULL); - qe.last_periodic_announce_sound = 0; - qe.valid_digits = 0; - if (!join_queue(args.queuename, &qe, &reason)) { - int makeannouncement = 0; - - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""), - S_OR(chan->cid.cid_num, "")); -check_turns: - if (ringing) { - ast_indicate(chan, AST_CONTROL_RINGING); - } else { - ast_moh_start(chan, qe.moh, NULL); - } - - /* This is the wait loop for callers 2 through maxlen */ - res = wait_our_turn(&qe, ringing, &reason); - if (res) - goto stop; - - for (;;) { - /* This is the wait loop for the head caller*/ - /* To exit, they may get their call answered; */ - /* they may dial a digit from the queue context; */ - /* or, they may timeout. */ - - enum queue_member_status stat; - - /* Leave if we have exceeded our queuetimeout */ - if (qe.expire && (time(NULL) >= qe.expire)) { - record_abandoned(&qe); - reason = QUEUE_TIMEOUT; - res = 0; - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); - break; - } - - if (makeannouncement) { - /* Make a position announcement, if enabled */ - if (qe.parent->announcefrequency && !ringing) - if ((res = say_position(&qe))) - goto stop; - - } - makeannouncement = 1; - - /* Leave if we have exceeded our queuetimeout */ - if (qe.expire && (time(NULL) >= qe.expire)) { - record_abandoned(&qe); - reason = QUEUE_TIMEOUT; - res = 0; - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); - break; - } - /* Make a periodic announcement, if enabled */ - if (qe.parent->periodicannouncefrequency && !ringing) - if ((res = say_periodic_announcement(&qe))) - goto stop; - - /* Leave if we have exceeded our queuetimeout */ - if (qe.expire && (time(NULL) >= qe.expire)) { - record_abandoned(&qe); - reason = QUEUE_TIMEOUT; - res = 0; - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); - break; - } - /* Try calling all queue members for 'timeout' seconds */ - res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi); - if (res) - goto stop; - - stat = get_member_status(qe.parent, qe.max_penalty); - - /* exit after 'timeout' cycle if 'n' option enabled */ - if (noption && tries >= qe.parent->membercount) { - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Exiting on time-out cycle\n"); - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); - record_abandoned(&qe); - reason = QUEUE_TIMEOUT; - res = 0; - break; - } - - /* leave the queue if no agents, if enabled */ - if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) { - record_abandoned(&qe); - reason = QUEUE_LEAVEEMPTY; - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start)); - res = 0; - break; - } - - /* leave the queue if no reachable agents, if enabled */ - if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) { - record_abandoned(&qe); - reason = QUEUE_LEAVEUNAVAIL; - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start)); - res = 0; - break; - } - - /* Leave if we have exceeded our queuetimeout */ - if (qe.expire && (time(NULL) >= qe.expire)) { - record_abandoned(&qe); - reason = QUEUE_TIMEOUT; - res = 0; - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); - break; - } - - /* If using dynamic realtime members, we should regenerate the member list for this queue */ - update_realtime_members(qe.parent); - - /* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */ - res = wait_a_bit(&qe); - if (res) - goto stop; - - /* Since this is a priority queue and - * it is not sure that we are still at the head - * of the queue, go and check for our turn again. - */ - if (!is_our_turn(&qe)) { - if (option_debug) - ast_log(LOG_DEBUG, "Darn priorities, going back in queue (%s)!\n", - qe.chan->name); - goto check_turns; - } - } - -stop: - if (res) { - if (res < 0) { - if (!qe.handled) { - record_abandoned(&qe); - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ABANDON", - "%d|%d|%ld", qe.pos, qe.opos, - (long) time(NULL) - qe.start); - } - res = -1; - } else if (qe.valid_digits) { - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY", - "%s|%d", qe.digits, qe.pos); - } - } - - /* Don't allow return code > 0 */ - if (res >= 0) { - res = 0; - if (ringing) { - ast_indicate(chan, -1); - } else { - ast_moh_stop(chan); - } - ast_stopstream(chan); - } - leave_queue(&qe); - if (reason != QUEUE_UNKNOWN) - set_queue_result(chan, reason); - } else { - ast_log(LOG_WARNING, "Unable to join queue '%s'\n", args.queuename); - set_queue_result(chan, reason); - res = 0; - } - ast_module_user_remove(lu); - - return res; -} - -static int queue_function_qac(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) -{ - int count = 0; - struct call_queue *q; - struct ast_module_user *lu; - struct member *m; - struct ao2_iterator mem_iter; - - buf[0] = '\0'; - - if (ast_strlen_zero(data)) { - ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); - return -1; - } - - lu = ast_module_user_add(chan); - - if ((q = load_realtime_queue(data))) { - ast_mutex_lock(&q->lock); - mem_iter = ao2_iterator_init(q->members, 0); - while ((m = ao2_iterator_next(&mem_iter))) { - /* Count the agents who are logged in and presently answering calls */ - if ((m->status != AST_DEVICE_UNAVAILABLE) && (m->status != AST_DEVICE_INVALID)) { - count++; - } - ao2_ref(m, -1); - } - ast_mutex_unlock(&q->lock); - } else - ast_log(LOG_WARNING, "queue %s was not found\n", data); - - snprintf(buf, len, "%d", count); - ast_module_user_remove(lu); - - return 0; -} - -static int queue_function_queuewaitingcount(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) -{ - int count = 0; - struct call_queue *q; - struct ast_module_user *lu; - struct ast_variable *var = NULL; - - buf[0] = '\0'; - - if (ast_strlen_zero(data)) { - ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); - return -1; - } - - lu = ast_module_user_add(chan); - - AST_LIST_LOCK(&queues); - AST_LIST_TRAVERSE(&queues, q, list) { - if (!strcasecmp(q->name, data)) { - ast_mutex_lock(&q->lock); - break; - } - } - AST_LIST_UNLOCK(&queues); - - if (q) { - count = q->count; - ast_mutex_unlock(&q->lock); - } else if ((var = ast_load_realtime("queues", "name", data, NULL))) { - /* if the queue is realtime but was not found in memory, this - * means that the queue had been deleted from memory since it was - * "dead." This means it has a 0 waiting count - */ - count = 0; - ast_variables_destroy(var); - } else - ast_log(LOG_WARNING, "queue %s was not found\n", data); - - snprintf(buf, len, "%d", count); - ast_module_user_remove(lu); - return 0; -} - -static int queue_function_queuememberlist(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) -{ - struct ast_module_user *u; - struct call_queue *q; - struct member *m; - - /* Ensure an otherwise empty list doesn't return garbage */ - buf[0] = '\0'; - - if (ast_strlen_zero(data)) { - ast_log(LOG_ERROR, "QUEUE_MEMBER_LIST requires an argument: queuename\n"); - return -1; - } - - u = ast_module_user_add(chan); - - AST_LIST_LOCK(&queues); - AST_LIST_TRAVERSE(&queues, q, list) { - if (!strcasecmp(q->name, data)) { - ast_mutex_lock(&q->lock); - break; - } - } - AST_LIST_UNLOCK(&queues); - - if (q) { - int buflen = 0, count = 0; - struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0); - - while ((m = ao2_iterator_next(&mem_iter))) { - /* strcat() is always faster than printf() */ - if (count++) { - strncat(buf + buflen, ",", len - buflen - 1); - buflen++; - } - strncat(buf + buflen, m->membername, len - buflen - 1); - buflen += strlen(m->membername); - /* Safeguard against overflow (negative length) */ - if (buflen >= len - 2) { - ao2_ref(m, -1); - ast_log(LOG_WARNING, "Truncating list\n"); - break; - } - ao2_ref(m, -1); - } - ast_mutex_unlock(&q->lock); - } else - ast_log(LOG_WARNING, "queue %s was not found\n", data); - - /* We should already be terminated, but let's make sure. */ - buf[len - 1] = '\0'; - ast_module_user_remove(u); - - return 0; -} - -static struct ast_custom_function queueagentcount_function = { - .name = "QUEUEAGENTCOUNT", - .synopsis = "Count number of agents answering a queue", - .syntax = "QUEUEAGENTCOUNT(<queuename>)", - .desc = -"Returns the number of members currently associated with the specified queue.\n" -"This function is deprecated. You should use QUEUE_MEMBER_COUNT() instead.\n", - .read = queue_function_qac, -}; - -static struct ast_custom_function queuemembercount_function = { - .name = "QUEUE_MEMBER_COUNT", - .synopsis = "Count number of members answering a queue", - .syntax = "QUEUE_MEMBER_COUNT(<queuename>)", - .desc = -"Returns the number of members currently associated with the specified queue.\n", - .read = queue_function_qac, -}; - -static struct ast_custom_function queuewaitingcount_function = { - .name = "QUEUE_WAITING_COUNT", - .synopsis = "Count number of calls currently waiting in a queue", - .syntax = "QUEUE_WAITING_COUNT(<queuename>)", - .desc = -"Returns the number of callers currently waiting in the specified queue.\n", - .read = queue_function_queuewaitingcount, -}; - -static struct ast_custom_function queuememberlist_function = { - .name = "QUEUE_MEMBER_LIST", - .synopsis = "Returns a list of interfaces on a queue", - .syntax = "QUEUE_MEMBER_LIST(<queuename>)", - .desc = -"Returns a comma-separated list of members associated with the specified queue.\n", - .read = queue_function_queuememberlist, -}; - -static int reload_queues(void) -{ - struct call_queue *q; - struct ast_config *cfg; - char *cat, *tmp; - struct ast_variable *var; - struct member *cur, *newm; - struct ao2_iterator mem_iter; - int new; - const char *general_val = NULL; - char parse[80]; - char *interface; - char *membername = NULL; - int penalty; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(interface); - AST_APP_ARG(penalty); - AST_APP_ARG(membername); - ); - - if (!(cfg = ast_config_load("queues.conf"))) { - ast_log(LOG_NOTICE, "No call queueing config file (queues.conf), so no call queues\n"); - return 0; - } - AST_LIST_LOCK(&queues); - use_weight=0; - /* Mark all non-realtime queues as dead for the moment */ - AST_LIST_TRAVERSE(&queues, q, list) { - if (!q->realtime) { - q->dead = 1; - q->found = 0; - } - } - - /* Chug through config file */ - cat = NULL; - while ((cat = ast_category_browse(cfg, cat)) ) { - if (!strcasecmp(cat, "general")) { - /* Initialize global settings */ - queue_persistent_members = 0; - if ((general_val = ast_variable_retrieve(cfg, "general", "persistentmembers"))) - queue_persistent_members = ast_true(general_val); - autofill_default = 0; - if ((general_val = ast_variable_retrieve(cfg, "general", "autofill"))) - autofill_default = ast_true(general_val); - montype_default = 0; - if ((general_val = ast_variable_retrieve(cfg, "general", "monitor-type"))) - if (!strcasecmp(general_val, "mixmonitor")) - montype_default = 1; - } else { /* Define queue */ - /* Look for an existing one */ - AST_LIST_TRAVERSE(&queues, q, list) { - if (!strcmp(q->name, cat)) - break; - } - if (!q) { - /* Make one then */ - if (!(q = alloc_queue(cat))) { - /* TODO: Handle memory allocation failure */ - } - new = 1; - } else - new = 0; - if (q) { - if (!new) - ast_mutex_lock(&q->lock); - /* Check if a queue with this name already exists */ - if (q->found) { - ast_log(LOG_WARNING, "Queue '%s' already defined! Skipping!\n", cat); - if (!new) - ast_mutex_unlock(&q->lock); - continue; - } - /* Re-initialize the queue, and clear statistics */ - init_queue(q); - clear_queue(q); - mem_iter = ao2_iterator_init(q->members, 0); - while ((cur = ao2_iterator_next(&mem_iter))) { - if (!cur->dynamic) { - cur->delme = 1; - } - ao2_ref(cur, -1); - } - for (var = ast_variable_browse(cfg, cat); var; var = var->next) { - if (!strcasecmp(var->name, "member")) { - struct member tmpmem; - membername = NULL; - - /* Add a new member */ - ast_copy_string(parse, var->value, sizeof(parse)); - - AST_NONSTANDARD_APP_ARGS(args, parse, ','); - - interface = args.interface; - if (!ast_strlen_zero(args.penalty)) { - tmp = args.penalty; - while (*tmp && *tmp < 33) tmp++; - penalty = atoi(tmp); - if (penalty < 0) { - penalty = 0; - } - } else - penalty = 0; - - if (!ast_strlen_zero(args.membername)) { - membername = args.membername; - while (*membername && *membername < 33) membername++; - } - - /* Find the old position in the list */ - ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); - cur = ao2_find(q->members, &tmpmem, OBJ_POINTER | OBJ_UNLINK); - - newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0); - ao2_link(q->members, newm); - ao2_ref(newm, -1); - newm = NULL; - - if (cur) - ao2_ref(cur, -1); - else { - /* Add them to the master int list if necessary */ - add_to_interfaces(interface); - q->membercount++; - } - } else { - queue_set_param(q, var->name, var->value, var->lineno, 1); - } - } - - /* Free remaining members marked as delme */ - mem_iter = ao2_iterator_init(q->members, 0); - while ((cur = ao2_iterator_next(&mem_iter))) { - if (! cur->delme) { - ao2_ref(cur, -1); - continue; - } - - q->membercount--; - ao2_unlink(q->members, cur); - remove_from_interfaces(cur->interface); - ao2_ref(cur, -1); - } - - if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN) - rr_dep_warning(); - - if (new) { - AST_LIST_INSERT_HEAD(&queues, q, list); - } else - ast_mutex_unlock(&q->lock); - } - } - } - ast_config_destroy(cfg); - AST_LIST_TRAVERSE_SAFE_BEGIN(&queues, q, list) { - if (q->dead) { - AST_LIST_REMOVE_CURRENT(&queues, list); - if (!q->count) - destroy_queue(q); - else - ast_log(LOG_DEBUG, "XXX Leaking a little memory :( XXX\n"); - } else { - ast_mutex_lock(&q->lock); - mem_iter = ao2_iterator_init(q->members, 0); - while ((cur = ao2_iterator_next(&mem_iter))) { - if (cur->dynamic) - q->membercount++; - cur->status = ast_device_state(cur->interface); - ao2_ref(cur, -1); - } - ast_mutex_unlock(&q->lock); - } - } - AST_LIST_TRAVERSE_SAFE_END; - AST_LIST_UNLOCK(&queues); - return 1; -} - -static int __queues_show(struct mansession *s, int manager, int fd, int argc, char **argv) -{ - struct call_queue *q; - struct queue_ent *qe; - struct member *mem; - int pos, queue_show; - time_t now; - char max_buf[150]; - char *max; - size_t max_left; - float sl = 0; - char *term = manager ? "\r\n" : "\n"; - struct ao2_iterator mem_iter; - - time(&now); - if (argc == 2) - queue_show = 0; - else if (argc == 3) - queue_show = 1; - else - return RESULT_SHOWUSAGE; - - /* We only want to load realtime queues when a specific queue is asked for. */ - if (queue_show) { - load_realtime_queue(argv[2]); - } else if (ast_check_realtime("queues")) { - struct ast_config *cfg = ast_load_realtime_multientry("queues", "name LIKE", "%", (char *) NULL); - char *queuename; - if (cfg) { - for (queuename = ast_category_browse(cfg, NULL); !ast_strlen_zero(queuename); queuename = ast_category_browse(cfg, queuename)) { - load_realtime_queue(queuename); - } - ast_config_destroy(cfg); - } - } - - AST_LIST_LOCK(&queues); - if (AST_LIST_EMPTY(&queues)) { - AST_LIST_UNLOCK(&queues); - if (queue_show) { - if (s) - astman_append(s, "No such queue: %s.%s",argv[2], term); - else - ast_cli(fd, "No such queue: %s.%s",argv[2], term); - } else { - if (s) - astman_append(s, "No queues.%s", term); - else - ast_cli(fd, "No queues.%s", term); - } - return RESULT_SUCCESS; - } - AST_LIST_TRAVERSE(&queues, q, list) { - ast_mutex_lock(&q->lock); - if (queue_show) { - if (strcasecmp(q->name, argv[2]) != 0) { - ast_mutex_unlock(&q->lock); - if (!AST_LIST_NEXT(q, list)) { - ast_cli(fd, "No such queue: %s.%s",argv[2], term); - break; - } - continue; - } - } - max_buf[0] = '\0'; - max = max_buf; - max_left = sizeof(max_buf); - if (q->maxlen) - ast_build_string(&max, &max_left, "%d", q->maxlen); - else - ast_build_string(&max, &max_left, "unlimited"); - sl = 0; - if (q->callscompleted > 0) - sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted); - if (s) - astman_append(s, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds%s", - q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight, - q->callscompleted, q->callsabandoned,sl,q->servicelevel, term); - else - ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds%s", - q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight, q->callscompleted, q->callsabandoned,sl,q->servicelevel, term); - if (ao2_container_count(q->members)) { - if (s) - astman_append(s, " Members: %s", term); - else - ast_cli(fd, " Members: %s", term); - mem_iter = ao2_iterator_init(q->members, 0); - while ((mem = ao2_iterator_next(&mem_iter))) { - max_buf[0] = '\0'; - max = max_buf; - max_left = sizeof(max_buf); - if (strcasecmp(mem->membername, mem->interface)) { - ast_build_string(&max, &max_left, " (%s)", mem->interface); - } - if (mem->penalty) - ast_build_string(&max, &max_left, " with penalty %d", mem->penalty); - if (mem->dynamic) - ast_build_string(&max, &max_left, " (dynamic)"); - if (mem->realtime) - ast_build_string(&max, &max_left, " (realtime)"); - if (mem->paused) - ast_build_string(&max, &max_left, " (paused)"); - ast_build_string(&max, &max_left, " (%s)", devstate2str(mem->status)); - if (mem->calls) { - ast_build_string(&max, &max_left, " has taken %d calls (last was %ld secs ago)", - mem->calls, (long) (time(NULL) - mem->lastcall)); - } else - ast_build_string(&max, &max_left, " has taken no calls yet"); - if (s) - astman_append(s, " %s%s%s", mem->membername, max_buf, term); - else - ast_cli(fd, " %s%s%s", mem->membername, max_buf, term); - ao2_ref(mem, -1); - } - } else if (s) - astman_append(s, " No Members%s", term); - else - ast_cli(fd, " No Members%s", term); - if (q->head) { - pos = 1; - if (s) - astman_append(s, " Callers: %s", term); - else - ast_cli(fd, " Callers: %s", term); - for (qe = q->head; qe; qe = qe->next) { - if (s) - astman_append(s, " %d. %s (wait: %ld:%2.2ld, prio: %d)%s", - pos++, qe->chan->name, (long) (now - qe->start) / 60, - (long) (now - qe->start) % 60, qe->prio, term); - else - ast_cli(fd, " %d. %s (wait: %ld:%2.2ld, prio: %d)%s", pos++, - qe->chan->name, (long) (now - qe->start) / 60, - (long) (now - qe->start) % 60, qe->prio, term); - } - } else if (s) - astman_append(s, " No Callers%s", term); - else - ast_cli(fd, " No Callers%s", term); - if (s) - astman_append(s, "%s", term); - else - ast_cli(fd, "%s", term); - ast_mutex_unlock(&q->lock); - if (queue_show) - break; - } - AST_LIST_UNLOCK(&queues); - return RESULT_SUCCESS; -} - -static int queue_show(int fd, int argc, char **argv) -{ - return __queues_show(NULL, 0, fd, argc, argv); -} - -static char *complete_queue(const char *line, const char *word, int pos, int state) -{ - struct call_queue *q; - char *ret = NULL; - int which = 0; - int wordlen = strlen(word); - - AST_LIST_LOCK(&queues); - AST_LIST_TRAVERSE(&queues, q, list) { - if (!strncasecmp(word, q->name, wordlen) && ++which > state) { - ret = ast_strdup(q->name); - break; - } - } - AST_LIST_UNLOCK(&queues); - - return ret; -} - -static char *complete_queue_show(const char *line, const char *word, int pos, int state) -{ - if (pos == 2) - return complete_queue(line, word, pos, state); - return NULL; -} - -/*!\brief callback to display queues status in manager - \addtogroup Group_AMI - */ -static int manager_queues_show(struct mansession *s, const struct message *m) -{ - char *a[] = { "queue", "show" }; - - __queues_show(s, 1, -1, 2, a); - astman_append(s, "\r\n\r\n"); /* Properly terminate Manager output */ - - return RESULT_SUCCESS; -} - -/* Dump queue status */ -static int manager_queues_status(struct mansession *s, const struct message *m) -{ - time_t now; - int pos; - const char *id = astman_get_header(m,"ActionID"); - const char *queuefilter = astman_get_header(m,"Queue"); - const char *memberfilter = astman_get_header(m,"Member"); - char idText[256] = ""; - struct call_queue *q; - struct queue_ent *qe; - float sl = 0; - struct member *mem; - struct ao2_iterator mem_iter; - - astman_send_ack(s, m, "Queue status will follow"); - time(&now); - AST_LIST_LOCK(&queues); - if (!ast_strlen_zero(id)) - snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); - - AST_LIST_TRAVERSE(&queues, q, list) { - ast_mutex_lock(&q->lock); - - /* List queue properties */ - if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) { - sl = ((q->callscompleted > 0) ? 100 * ((float)q->callscompletedinsl / (float)q->callscompleted) : 0); - astman_append(s, "Event: QueueParams\r\n" - "Queue: %s\r\n" - "Max: %d\r\n" - "Calls: %d\r\n" - "Holdtime: %d\r\n" - "Completed: %d\r\n" - "Abandoned: %d\r\n" - "ServiceLevel: %d\r\n" - "ServicelevelPerf: %2.1f\r\n" - "Weight: %d\r\n" - "%s" - "\r\n", - q->name, q->maxlen, q->count, q->holdtime, q->callscompleted, - q->callsabandoned, q->servicelevel, sl, q->weight, idText); - /* List Queue Members */ - mem_iter = ao2_iterator_init(q->members, 0); - while ((mem = ao2_iterator_next(&mem_iter))) { - if (ast_strlen_zero(memberfilter) || !strcmp(mem->interface, memberfilter)) { - astman_append(s, "Event: QueueMember\r\n" - "Queue: %s\r\n" - "Name: %s\r\n" - "Location: %s\r\n" - "Membership: %s\r\n" - "Penalty: %d\r\n" - "CallsTaken: %d\r\n" - "LastCall: %d\r\n" - "Status: %d\r\n" - "Paused: %d\r\n" - "%s" - "\r\n", - q->name, mem->membername, mem->interface, mem->dynamic ? "dynamic" : "static", - mem->penalty, mem->calls, (int)mem->lastcall, mem->status, mem->paused, idText); - } - ao2_ref(mem, -1); - } - /* List Queue Entries */ - pos = 1; - for (qe = q->head; qe; qe = qe->next) { - astman_append(s, "Event: QueueEntry\r\n" - "Queue: %s\r\n" - "Position: %d\r\n" - "Channel: %s\r\n" - "CallerID: %s\r\n" - "CallerIDName: %s\r\n" - "Wait: %ld\r\n" - "%s" - "\r\n", - q->name, pos++, qe->chan->name, - S_OR(qe->chan->cid.cid_num, "unknown"), - S_OR(qe->chan->cid.cid_name, "unknown"), - (long) (now - qe->start), idText); - } - } - ast_mutex_unlock(&q->lock); - } - - astman_append(s, - "Event: QueueStatusComplete\r\n" - "%s" - "\r\n",idText); - - AST_LIST_UNLOCK(&queues); - - - return RESULT_SUCCESS; -} - -static int manager_add_queue_member(struct mansession *s, const struct message *m) -{ - const char *queuename, *interface, *penalty_s, *paused_s, *membername; - int paused, penalty = 0; - - queuename = astman_get_header(m, "Queue"); - interface = astman_get_header(m, "Interface"); - penalty_s = astman_get_header(m, "Penalty"); - paused_s = astman_get_header(m, "Paused"); - membername = astman_get_header(m, "MemberName"); - - if (ast_strlen_zero(queuename)) { - astman_send_error(s, m, "'Queue' not specified."); - return 0; - } - - if (ast_strlen_zero(interface)) { - astman_send_error(s, m, "'Interface' not specified."); - return 0; - } - - if (ast_strlen_zero(penalty_s)) - penalty = 0; - else if (sscanf(penalty_s, "%d", &penalty) != 1 || penalty < 0) - penalty = 0; - - if (ast_strlen_zero(paused_s)) - paused = 0; - else - paused = abs(ast_true(paused_s)); - - switch (add_to_queue(queuename, interface, membername, penalty, paused, queue_persistent_members)) { - case RES_OKAY: - ast_queue_log(queuename, "MANAGER", interface, "ADDMEMBER", "%s", ""); - astman_send_ack(s, m, "Added interface to queue"); - break; - case RES_EXISTS: - astman_send_error(s, m, "Unable to add interface: Already there"); - break; - case RES_NOSUCHQUEUE: - astman_send_error(s, m, "Unable to add interface to queue: No such queue"); - break; - case RES_OUTOFMEMORY: - astman_send_error(s, m, "Out of memory"); - break; - } - - return 0; -} - -static int manager_remove_queue_member(struct mansession *s, const struct message *m) -{ - const char *queuename, *interface; - - queuename = astman_get_header(m, "Queue"); - interface = astman_get_header(m, "Interface"); - - if (ast_strlen_zero(queuename) || ast_strlen_zero(interface)) { - astman_send_error(s, m, "Need 'Queue' and 'Interface' parameters."); - return 0; - } - - switch (remove_from_queue(queuename, interface)) { - case RES_OKAY: - ast_queue_log(queuename, "MANAGER", interface, "REMOVEMEMBER", "%s", ""); - astman_send_ack(s, m, "Removed interface from queue"); - break; - case RES_EXISTS: - astman_send_error(s, m, "Unable to remove interface: Not there"); - break; - case RES_NOSUCHQUEUE: - astman_send_error(s, m, "Unable to remove interface from queue: No such queue"); - break; - case RES_OUTOFMEMORY: - astman_send_error(s, m, "Out of memory"); - break; - case RES_NOT_DYNAMIC: - astman_send_error(s, m, "Member not dynamic"); - break; - } - - return 0; -} - -static int manager_pause_queue_member(struct mansession *s, const struct message *m) -{ - const char *queuename, *interface, *paused_s; - int paused; - - interface = astman_get_header(m, "Interface"); - paused_s = astman_get_header(m, "Paused"); - queuename = astman_get_header(m, "Queue"); /* Optional - if not supplied, pause the given Interface in all queues */ - - if (ast_strlen_zero(interface) || ast_strlen_zero(paused_s)) { - astman_send_error(s, m, "Need 'Interface' and 'Paused' parameters."); - return 0; - } - - paused = abs(ast_true(paused_s)); - - if (set_member_paused(queuename, interface, paused)) - astman_send_error(s, m, "Interface not found"); - else - astman_send_ack(s, m, paused ? "Interface paused successfully" : "Interface unpaused successfully"); - return 0; -} - -static int handle_queue_add_member(int fd, int argc, char *argv[]) -{ - char *queuename, *interface, *membername = NULL; - int penalty; - - if ((argc != 6) && (argc != 8) && (argc != 10)) { - return RESULT_SHOWUSAGE; - } else if (strcmp(argv[4], "to")) { - return RESULT_SHOWUSAGE; - } else if ((argc == 8) && strcmp(argv[6], "penalty")) { - return RESULT_SHOWUSAGE; - } else if ((argc == 10) && strcmp(argv[8], "as")) { - return RESULT_SHOWUSAGE; - } - - queuename = argv[5]; - interface = argv[3]; - if (argc >= 8) { - if (sscanf(argv[7], "%d", &penalty) == 1) { - if (penalty < 0) { - ast_cli(fd, "Penalty must be >= 0\n"); - penalty = 0; - } - } else { - ast_cli(fd, "Penalty must be an integer >= 0\n"); - penalty = 0; - } - } else { - penalty = 0; - } - - if (argc >= 10) { - membername = argv[9]; - } - - switch (add_to_queue(queuename, interface, membername, penalty, 0, queue_persistent_members)) { - case RES_OKAY: - ast_queue_log(queuename, "CLI", interface, "ADDMEMBER", "%s", ""); - ast_cli(fd, "Added interface '%s' to queue '%s'\n", interface, queuename); - return RESULT_SUCCESS; - case RES_EXISTS: - ast_cli(fd, "Unable to add interface '%s' to queue '%s': Already there\n", interface, queuename); - return RESULT_FAILURE; - case RES_NOSUCHQUEUE: - ast_cli(fd, "Unable to add interface to queue '%s': No such queue\n", queuename); - return RESULT_FAILURE; - case RES_OUTOFMEMORY: - ast_cli(fd, "Out of memory\n"); - return RESULT_FAILURE; - default: - return RESULT_FAILURE; - } -} - -static char *complete_queue_add_member(const char *line, const char *word, int pos, int state) -{ - /* 0 - queue; 1 - add; 2 - member; 3 - <interface>; 4 - to; 5 - <queue>; 6 - penalty; 7 - <penalty>; 8 - as; 9 - <membername> */ - switch (pos) { - case 3: /* Don't attempt to complete name of interface (infinite possibilities) */ - return NULL; - case 4: /* only one possible match, "to" */ - return state == 0 ? ast_strdup("to") : NULL; - case 5: /* <queue> */ - return complete_queue(line, word, pos, state); - case 6: /* only one possible match, "penalty" */ - return state == 0 ? ast_strdup("penalty") : NULL; - case 7: - if (state < 100) { /* 0-99 */ - char *num; - if ((num = ast_malloc(3))) { - sprintf(num, "%d", state); - } - return num; - } else { - return NULL; - } - case 8: /* only one possible match, "as" */ - return state == 0 ? ast_strdup("as") : NULL; - case 9: /* Don't attempt to complete name of member (infinite possibilities) */ - return NULL; - default: - return NULL; - } -} - -static int handle_queue_remove_member(int fd, int argc, char *argv[]) -{ - char *queuename, *interface; - - if (argc != 6) { - return RESULT_SHOWUSAGE; - } else if (strcmp(argv[4], "from")) { - return RESULT_SHOWUSAGE; - } - - queuename = argv[5]; - interface = argv[3]; - - switch (remove_from_queue(queuename, interface)) { - case RES_OKAY: - ast_queue_log(queuename, "CLI", interface, "REMOVEMEMBER", "%s", ""); - ast_cli(fd, "Removed interface '%s' from queue '%s'\n", interface, queuename); - return RESULT_SUCCESS; - case RES_EXISTS: - ast_cli(fd, "Unable to remove interface '%s' from queue '%s': Not there\n", interface, queuename); - return RESULT_FAILURE; - case RES_NOSUCHQUEUE: - ast_cli(fd, "Unable to remove interface from queue '%s': No such queue\n", queuename); - return RESULT_FAILURE; - case RES_OUTOFMEMORY: - ast_cli(fd, "Out of memory\n"); - return RESULT_FAILURE; - case RES_NOT_DYNAMIC: - ast_cli(fd, "Member not dynamic\n"); - return RESULT_FAILURE; - default: - return RESULT_FAILURE; - } -} - -static char *complete_queue_remove_member(const char *line, const char *word, int pos, int state) -{ - int which = 0; - struct call_queue *q; - struct member *m; - struct ao2_iterator mem_iter; - - /* 0 - queue; 1 - remove; 2 - member; 3 - <member>; 4 - from; 5 - <queue> */ - if (pos > 5 || pos < 3) - return NULL; - if (pos == 4) /* only one possible match, 'from' */ - return state == 0 ? ast_strdup("from") : NULL; - - if (pos == 5) /* No need to duplicate code */ - return complete_queue(line, word, pos, state); - - /* here is the case for 3, <member> */ - if (!AST_LIST_EMPTY(&queues)) { /* XXX unnecessary ? the traverse does that for us */ - AST_LIST_TRAVERSE(&queues, q, list) { - ast_mutex_lock(&q->lock); - mem_iter = ao2_iterator_init(q->members, 0); - while ((m = ao2_iterator_next(&mem_iter))) { - if (++which > state) { - char *tmp; - ast_mutex_unlock(&q->lock); - tmp = ast_strdup(m->interface); - ao2_ref(m, -1); - return tmp; - } - ao2_ref(m, -1); - } - ast_mutex_unlock(&q->lock); - } - } - - return NULL; -} - -static char queue_show_usage[] = -"Usage: queue show\n" -" Provides summary information on a specified queue.\n"; - -static char qam_cmd_usage[] = -"Usage: queue add member <channel> to <queue> [penalty <penalty>]\n"; - -static char qrm_cmd_usage[] = -"Usage: queue remove member <channel> from <queue>\n"; - -static struct ast_cli_entry cli_show_queue_deprecated = { - { "show", "queue", NULL }, - queue_show, NULL, - NULL, complete_queue_show }; - -static struct ast_cli_entry cli_add_queue_member_deprecated = { - { "add", "queue", "member", NULL }, - handle_queue_add_member, NULL, - NULL, complete_queue_add_member }; - -static struct ast_cli_entry cli_remove_queue_member_deprecated = { - { "remove", "queue", "member", NULL }, - handle_queue_remove_member, NULL, - NULL, complete_queue_remove_member }; - -static struct ast_cli_entry cli_queue[] = { - /* Deprecated */ - { { "show", "queues", NULL }, - queue_show, NULL, - NULL, NULL }, - - { { "queue", "show", NULL }, - queue_show, "Show status of a specified queue", - queue_show_usage, complete_queue_show, &cli_show_queue_deprecated }, - - { { "queue", "add", "member", NULL }, - handle_queue_add_member, "Add a channel to a specified queue", - qam_cmd_usage, complete_queue_add_member, &cli_add_queue_member_deprecated }, - - { { "queue", "remove", "member", NULL }, - handle_queue_remove_member, "Removes a channel from a specified queue", - qrm_cmd_usage, complete_queue_remove_member, &cli_remove_queue_member_deprecated }, -}; - -static int unload_module(void) -{ - int res; - - if (device_state.thread != AST_PTHREADT_NULL) { - device_state.stop = 1; - ast_mutex_lock(&device_state.lock); - ast_cond_signal(&device_state.cond); - ast_mutex_unlock(&device_state.lock); - pthread_join(device_state.thread, NULL); - } - - ast_cli_unregister_multiple(cli_queue, sizeof(cli_queue) / sizeof(struct ast_cli_entry)); - res = ast_manager_unregister("QueueStatus"); - res |= ast_manager_unregister("Queues"); - res |= ast_manager_unregister("QueueAdd"); - res |= ast_manager_unregister("QueueRemove"); - res |= ast_manager_unregister("QueuePause"); - res |= ast_unregister_application(app_aqm); - res |= ast_unregister_application(app_rqm); - res |= ast_unregister_application(app_pqm); - res |= ast_unregister_application(app_upqm); - res |= ast_unregister_application(app_ql); - res |= ast_unregister_application(app); - res |= ast_custom_function_unregister(&queueagentcount_function); - res |= ast_custom_function_unregister(&queuemembercount_function); - res |= ast_custom_function_unregister(&queuememberlist_function); - res |= ast_custom_function_unregister(&queuewaitingcount_function); - ast_devstate_del(statechange_queue, NULL); - - ast_module_user_hangup_all(); - - clear_and_free_interfaces(); - - return res; -} - -static int load_module(void) -{ - int res; - - if (!reload_queues()) - return AST_MODULE_LOAD_DECLINE; - - if (queue_persistent_members) - reload_queue_members(); - - ast_mutex_init(&device_state.lock); - ast_cond_init(&device_state.cond, NULL); - ast_pthread_create(&device_state.thread, NULL, device_state_thread, NULL); - - ast_cli_register_multiple(cli_queue, sizeof(cli_queue) / sizeof(struct ast_cli_entry)); - res = ast_register_application(app, queue_exec, synopsis, descrip); - res |= ast_register_application(app_aqm, aqm_exec, app_aqm_synopsis, app_aqm_descrip); - res |= ast_register_application(app_rqm, rqm_exec, app_rqm_synopsis, app_rqm_descrip); - res |= ast_register_application(app_pqm, pqm_exec, app_pqm_synopsis, app_pqm_descrip); - res |= ast_register_application(app_upqm, upqm_exec, app_upqm_synopsis, app_upqm_descrip); - res |= ast_register_application(app_ql, ql_exec, app_ql_synopsis, app_ql_descrip); - res |= ast_manager_register("Queues", 0, manager_queues_show, "Queues"); - res |= ast_manager_register("QueueStatus", 0, manager_queues_status, "Queue Status"); - res |= ast_manager_register("QueueAdd", EVENT_FLAG_AGENT, manager_add_queue_member, "Add interface to queue."); - res |= ast_manager_register("QueueRemove", EVENT_FLAG_AGENT, manager_remove_queue_member, "Remove interface from queue."); - res |= ast_manager_register("QueuePause", EVENT_FLAG_AGENT, manager_pause_queue_member, "Makes a queue member temporarily unavailable"); - res |= ast_custom_function_register(&queueagentcount_function); - res |= ast_custom_function_register(&queuemembercount_function); - res |= ast_custom_function_register(&queuememberlist_function); - res |= ast_custom_function_register(&queuewaitingcount_function); - res |= ast_devstate_add(statechange_queue, NULL); - - return res; -} - -static int reload(void) -{ - reload_queues(); - return 0; -} - -AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "True Call Queueing", - .load = load_module, - .unload = unload_module, - .reload = reload, - ); - |