Index: apps/app_queue.c
===================================================================
--- apps/app_queue.c	(revision 114901)
+++ apps/app_queue.c	(working copy)
@@ -340,6 +340,7 @@
 	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 */
+	AST_LIST_ENTRY(queue_ent) list;
 };
 
 struct member {
@@ -433,10 +434,21 @@
 	 *       in, so this can not simply be replaced with ao2_container_count(). 
 	 */
 	int membercount;
-	struct queue_ent *head;             /*!< Head of the list of callers */
+	int group_found;                    /*!< How to tell if a group has been specified for a queue */
+	int group_strategy_found;			/*!< How to tell if a queue's group specifies the strategy to use */
+	struct queue_group *group;          /*!< To which group does this queue belong */
 	AST_LIST_ENTRY(call_queue) list;    /*!< Next call queue */
 };
 
+struct queue_group {
+	AST_LIST_HEAD(, queue_ent) callers;
+	AST_LIST_ENTRY(queue_group) list;
+	char name[80];
+	int unused;                         /*!< If a group is removed from configuration, this is how we detect it */
+};
+
+static AST_LIST_HEAD_STATIC(groups, queue_group);
+
 static AST_LIST_HEAD_STATIC(queues, call_queue);
 
 static int set_member_paused(const char *queuename, const char *interface, int paused);
@@ -496,26 +508,6 @@
 	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,
@@ -802,6 +794,8 @@
 {
 	int i;
 
+	q->group_found = 0;
+	q->group_strategy_found = 0;
 	q->dead = 0;
 	q->retry = DEFAULT_RETRY;
 	q->timeout = -1;
@@ -941,6 +935,124 @@
 	AST_LIST_UNLOCK(&interfaces);
 }
 
+static void parse_member(struct call_queue *q, char *data)
+{
+	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);
+	);
+	struct member *newm = NULL, *cur = NULL;
+	struct member tmpmem;
+	char *tmp;
+
+	/* Add a new member */
+	ast_copy_string(parse, data, 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++;
+	} else
+		membername = interface;
+
+	/* 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++;
+	}
+}
+static void set_strategy(struct call_queue *q, const char *strategy)
+{
+	q->strategy = strat2int(strategy);
+	if (q->strategy < 0) {
+		ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
+			strategy, q->name);
+		q->strategy = QUEUE_STRATEGY_RINGALL;
+	}
+}
+static void parse_queuegroup(struct call_queue *q, const char *groupname)
+{
+	struct ast_config *groupcfg = NULL;
+	struct ast_variable *var = NULL, *realtimevar = NULL;
+	struct queue_group *group = NULL;
+
+	/* As with other queue-related configurations, static trumps realtime.
+	 * In other words, if we find the specified group in the config file, then
+	 * we use it. We only look in realtime if we can't find the group in the config file
+	 */
+	if (!(groupcfg = ast_config_load("queuegroups.conf"))) {
+		if (!(realtimevar = ast_load_realtime("queue_groups", "name", groupname, NULL))) {
+			ast_log(LOG_WARNING, "Group %s was specified for queue %s but could not find definition. Ignoring!\n", groupname, q->name);
+			return;
+		}
+	}
+
+	for (var = realtimevar ? realtimevar : ast_variable_browse(groupcfg, groupname); var; var = var->next) {
+		if (!strcasecmp(var->name, "member")) {
+			parse_member(q, var->value);
+		} else if (!strcasecmp(var->name, "strategy")) {
+			set_strategy(q, var->value);
+			q->group_strategy_found = 1;
+		} else {
+			ast_log(LOG_WARNING, "Invalid queuegroup option '%s' for queuegroup '%s'\n", var->name, groupname);
+		}
+	}
+
+	if (groupcfg) {
+		ast_config_destroy(groupcfg);
+	} else {
+		ast_variables_destroy(realtimevar);
+	}
+
+	AST_LIST_TRAVERSE(&groups, group, list) {
+		if (!strcasecmp(group->name, groupname)) {
+			group->unused = 0;
+			q->group = group;
+			q->group_found = 1;
+			return;
+		}
+	}
+
+	/* This is a new group */
+	if (!(group = ast_calloc(1, sizeof (*group)))) {
+		/* OH SHIT */
+	}
+	ast_copy_string(group->name, groupname, sizeof(group->name));
+	AST_LIST_HEAD_INIT(&group->callers);
+	group->unused = 0;
+	q->group = group;
+	q->group_found = 1;
+	AST_LIST_INSERT_HEAD(&groups, group, list);
+}
+
 /*! \brief Configure a queue parameter.
 \par
    For error reporting, line number is passed for .conf static configuration.
@@ -1046,11 +1158,10 @@
 	} 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;
+		if (!q->group_strategy_found){
+			set_strategy(q, val);
+		} else {
+			ast_log(LOG_WARNING, "Ignoring strategy parameter for queue %s since its group specifies a strategy already\n", q->name);
 		}
 	} else if (!strcasecmp(param, "joinempty")) {
 		if (!strcasecmp(val, "strict"))
@@ -1086,6 +1197,12 @@
 		   we will not see any effect on use_weight until next reload. */
 	} else if (!strcasecmp(param, "timeoutrestart")) {
 		q->timeoutrestart = ast_true(val);
+	} else if (!strcasecmp(param, "group")) {
+		if (!q->group_found) {
+			parse_queuegroup(q, val);
+		} else {
+			ast_log(LOG_WARNING, "Ignoring group setting %s for queue %s since a group was already specified\n", val, q->name);
+		}
 	} else if (failunknown) {
 		if (linenum >= 0) {
 			ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queues.conf\n",
@@ -1406,7 +1523,7 @@
 static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason)
 {
 	struct call_queue *q;
-	struct queue_ent *cur, *prev = NULL;
+	struct queue_ent *cur;
 	int res = -1;
 	int pos = 0;
 	int inserted = 0;
@@ -1431,23 +1548,33 @@
 		 * the queue.
 		 * Take into account the priority of the calling user */
 		inserted = 0;
-		prev = NULL;
-		cur = q->head;
-		while (cur) {
+		AST_LIST_LOCK(&q->group->callers);
+		AST_LIST_TRAVERSE_SAFE_BEGIN(&q->group->callers, cur, list) {
 			/* 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);
+				AST_LIST_INSERT_BEFORE_CURRENT(&q->group->callers, qe, list);
+				if (option_debug > 2)
+					ast_log(LOG_DEBUG, "Inserted %s into group %s, address %p\n", qe->chan->name, q->group->name, q->group);
+				qe->parent = q;
+				qe->pos = ++pos;
+				qe->opos = pos;
 				inserted = 1;
 			}
 			cur->pos = ++pos;
-			prev = cur;
-			cur = cur->next;
 		}
+		AST_LIST_TRAVERSE_SAFE_END;
 		/* No luck, join at the end of the queue */
-		if (!inserted)
-			insert_entry(q, prev, qe, &pos);
+		if (!inserted) {
+			AST_LIST_INSERT_TAIL(&q->group->callers, qe, list);
+			if (option_debug > 2)
+				ast_log(LOG_DEBUG, "Inserted %s into group %s, address %p\n", qe->chan->name, q->group->name, q->group);
+			qe->parent = q;
+			qe->pos = ++pos;
+			qe->opos = pos;
+		}
+		AST_LIST_UNLOCK(&q->group->callers);
 		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));
@@ -1649,10 +1776,10 @@
 	ast_mutex_lock(&q->lock);
 
 	prev = NULL;
-	for (cur = q->head; cur; cur = cur->next) {
+	AST_LIST_LOCK(&q->group->callers);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&q->group->callers, cur, list) {
 		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",
@@ -1660,16 +1787,14 @@
 			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;
+			AST_LIST_REMOVE_CURRENT(&q->group->callers, list);
 		} else {
 			/* Renumber the people after us in the queue based on a new count */
 			cur->pos = ++pos;
-			prev = cur;
 		}
 	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_UNLOCK(&q->group->callers);
 	ast_mutex_unlock(&q->lock);
 
 	if (q->dead && !q->count) {	
@@ -2335,7 +2460,7 @@
 
 	if (!qe->parent->autofill) {
 		/* Atomically read the parent head -- does not need a lock */
-		ch = qe->parent->head;
+		ch = AST_LIST_FIRST(&qe->parent->group->callers);
 		/* If we are now at the top of the head, break out */
 		if (ch == qe) {
 			if (option_debug)
@@ -2351,8 +2476,6 @@
 		/* 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");
@@ -2378,11 +2501,16 @@
 		if (option_debug)
 			ast_log(LOG_DEBUG, "There are %d available members.\n", avl);
 	
-		while ((idx < avl) && (ch) && (ch != qe)) {
-			if (!ch->pending)
+		AST_LIST_LOCK(&qe->parent->group->callers);
+		AST_LIST_TRAVERSE(&qe->parent->group->callers, ch, list) {
+			if (idx >= avl || ch == qe) {
+				break;
+			}
+			if (!ch->pending) {
 				idx++;
-			ch = ch->next;			
+			}
 		}
+		AST_LIST_UNLOCK(&qe->parent->group->callers);
 	
 		/* If the queue entry is within avl [the number of available members] calls from the top ... */
 		if (ch && idx < avl) {
@@ -4110,30 +4238,26 @@
 	.read = queue_function_queuememberlist,
 };
 
+
+
 static int reload_queues(void)
 {
 	struct call_queue *q;
 	struct ast_config *cfg;
-	char *cat, *tmp;
+	struct member *cur;
+	char *cat;
 	struct ast_variable *var;
-	struct member *cur, *newm;
 	struct ao2_iterator mem_iter;
+	struct queue_group *group = NULL;
 	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 */
@@ -4143,6 +4267,10 @@
 			q->found = 0;
 		}
 	}
+	/* And mark the queue groups as unused */
+	AST_LIST_TRAVERSE(&groups, group, list) {
+		group->unused = 1;
+	}
 
 	/* Chug through config file */
 	cat = NULL;
@@ -4195,51 +4323,28 @@
 				}
 				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++;
-						}
+						parse_member(q, var->value);
 					} else {
 						queue_set_param(q, var->name, var->value, var->lineno, 1);
 					}
 				}
 
+				if (!q->group_found) {
+					/* No group was specified for this queue, so we need to
+					 * figure out if the queue used to be part of a group and if
+					 * so, was it a named one.
+					 */
+					if (!q->group || !ast_strlen_zero(q->group->name)) {
+						/* Either this is a new queue or the queue was in a group
+						 * but now is not
+						 */
+						group = ast_calloc(1, sizeof(*group));
+						*(group->name) = '\0';
+						AST_LIST_HEAD_INIT(&group->callers);
+						q->group = group;
+					}
+				}
+
 				/* Free remaining members marked as delme */
 				mem_iter = ao2_iterator_init(q->members, 0);
 				while ((cur = ao2_iterator_next(&mem_iter))) {
@@ -4286,6 +4391,13 @@
 	}
 	AST_LIST_TRAVERSE_SAFE_END;
 	AST_LIST_UNLOCK(&queues);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&groups, group, list) {
+		if (group->unused) {
+			AST_LIST_REMOVE_CURRENT(&groups, list);
+			free(group);
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
 	return 1;
 }
 
@@ -4394,13 +4506,14 @@
 			astman_append(s, "   No Members%s", term);
 		else	
 			ast_cli(fd, "   No Members%s", term);
-		if (q->head) {
+		AST_LIST_LOCK(&q->group->callers);
+		if (!AST_LIST_EMPTY(&q->group->callers)) {
 			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) {
+			AST_LIST_TRAVERSE(&q->group->callers, qe, list) {
 				if (s)
 					astman_append(s, "      %d. %s (wait: %ld:%2.2ld, prio: %d)%s",
 						pos++, qe->chan->name, (long) (now - qe->start) / 60,
@@ -4414,6 +4527,7 @@
 			astman_append(s, "   No Callers%s", term);
 		else
 			ast_cli(fd, "   No Callers%s", term);
+		AST_LIST_UNLOCK(&q->group->callers);
 		if (s)
 			astman_append(s, "%s", term);
 		else
@@ -4534,7 +4648,8 @@
 			}
 			/* List Queue Entries */
 			pos = 1;
-			for (qe = q->head; qe; qe = qe->next) {
+			AST_LIST_LOCK(&q->group->callers);
+			AST_LIST_TRAVERSE(&q->group->callers, qe, list) {
 				astman_append(s, "Event: QueueEntry\r\n"
 					"Queue: %s\r\n"
 					"Position: %d\r\n"
@@ -4549,6 +4664,7 @@
 					S_OR(qe->chan->cid.cid_name, "unknown"),
 					(long) (now - qe->start), idText);
 			}
+			AST_LIST_UNLOCK(&q->group->callers);
 		}
 		ast_mutex_unlock(&q->lock);
 	}
@@ -4829,6 +4945,140 @@
 	return NULL;
 }
 
+static int handle_queue_group_show(int fd, int argc, char *argv[])
+{
+	char *groupname = NULL;
+	struct queue_group *group_iter;
+	struct call_queue *queue_iter;
+	struct queue_ent *qe;
+	int pos;
+	time_t now;
+	char *max;
+	char max_buf[80];
+	size_t max_left;
+	float sl = 0;
+
+	time(&now);
+
+	if (argc > 3)
+		groupname = argv[3];
+
+	AST_LIST_TRAVERSE(&groups, group_iter, list) {
+		if (ast_strlen_zero(groupname) || !strcasecmp(group_iter->name, groupname)) {
+			ast_cli(fd, "Groupname: %s\n", group_iter->name); 
+			ast_cli(fd, "Queues:\n");
+			AST_LIST_TRAVERSE(&queues, queue_iter, list) {
+				max_buf[0] = '\0';
+				max = max_buf;
+				max_left = sizeof(max_buf);
+				sl = 0;
+				if (queue_iter->callscompleted > 0)
+					sl = 100 * ((float) queue_iter->callscompletedinsl / (float) queue_iter->callscompleted);
+				if (queue_iter->maxlen)
+					ast_build_string(&max, &max_left, "%d", queue_iter->maxlen);
+				else
+					ast_build_string(&max, &max_left, "unlimited");
+				if (!strcasecmp(queue_iter->group->name, group_iter->name)) {
+					ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, S:%2.1f within %ds\n",
+						queue_iter->name, queue_iter->count, max_buf, int2strat(queue_iter->strategy), queue_iter->holdtime, queue_iter->weight, queue_iter->callscompleted, queue_iter->callsabandoned,sl,queue_iter->servicelevel);
+				}
+			}
+			AST_LIST_LOCK(&group_iter->callers);
+			if (!AST_LIST_EMPTY(&group_iter->callers)) {
+				pos = 1;
+				ast_cli(fd, "   Callers: \n");
+				AST_LIST_TRAVERSE(&group_iter->callers, qe, list) {
+					ast_cli(fd, "      %d. %s (wait: %ld:%2.2ld, prio: %d)\n", pos++,
+						qe->chan->name, (long) (now - qe->start) / 60,
+						(long) (now - qe->start) % 60, qe->prio);
+				}
+			} else
+				ast_cli(fd, "   No Callers\n");
+			AST_LIST_UNLOCK(&group_iter->callers);
+		}
+	}
+
+	return RESULT_SUCCESS;
+}
+
+static char *complete_queue_group_show(const char *line, const char *word, int pos, int state)
+{
+	struct queue_group *group;
+	char *ret = NULL;
+	int which = 0;
+	int wordlen = strlen(word);
+
+	if (pos != 3)
+		return NULL;
+	
+	AST_LIST_LOCK(&groups);
+	AST_LIST_TRAVERSE(&groups, group, list) {
+		if (!strncasecmp(word, group->name, wordlen) && ++which > state) {
+			ret = ast_strdup(group->name);	
+			break;
+		}
+	}
+	AST_LIST_UNLOCK(&groups);
+
+	return ret;
+}
+
+
+static int manager_queue_group_show(struct mansession *s, const struct message *m)
+{
+	const char *groupname = astman_get_header(m, "Group");
+	struct queue_group *group_iter;
+	struct call_queue *queue_iter;
+	struct queue_ent *qe;
+	int pos;
+	time_t now;
+	char *max;
+	char max_buf[80];
+	size_t max_left;
+	float sl = 0;
+
+	time(&now);
+
+	AST_LIST_TRAVERSE(&groups, group_iter, list) {
+		if (ast_strlen_zero(groupname) || !strcasecmp(group_iter->name, groupname)) {
+			astman_append(s, "Groupname: %s\r\n", group_iter->name); 
+			astman_append(s, "Queues:\r\n");
+			AST_LIST_TRAVERSE(&queues, queue_iter, list) {
+				max_buf[0] = '\0';
+				max = max_buf;
+				max_left = sizeof(max_buf);
+				sl = 0;
+				if (queue_iter->callscompleted > 0)
+					sl = 100 * ((float) queue_iter->callscompletedinsl / (float) queue_iter->callscompleted);
+				if (queue_iter->maxlen)
+					ast_build_string(&max, &max_left, "%d", queue_iter->maxlen);
+				else
+					ast_build_string(&max, &max_left, "unlimited");
+
+				if (!strcasecmp(queue_iter->group->name, group_iter->name)) {
+					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\r\n",
+						queue_iter->name, queue_iter->count, max_buf, int2strat(queue_iter->strategy), queue_iter->holdtime, queue_iter->weight,
+						queue_iter->callscompleted, queue_iter->callsabandoned,sl,queue_iter->servicelevel);
+				}
+			}
+			AST_LIST_LOCK(&group_iter->callers);
+			if (!AST_LIST_EMPTY(&group_iter->callers)) {
+				pos = 1;
+				astman_append(s, "   Callers: \n");
+				AST_LIST_TRAVERSE(&group_iter->callers, qe, list) {
+					astman_append(s, "      %d. %s (wait: %ld:%2.2ld, prio: %d)\r\n", pos++,
+						qe->chan->name, (long) (now - qe->start) / 60,
+						(long) (now - qe->start) % 60, qe->prio);
+				}
+			} else
+				astman_append(s, "   No Callers\n");
+			AST_LIST_UNLOCK(&group_iter->callers);
+		}
+	}
+
+	return 0;
+}
+
 static char queue_show_usage[] =
 "Usage: queue show\n"
 "       Provides summary information on a specified queue.\n";
@@ -4839,6 +5089,11 @@
 static char qrm_cmd_usage[] =
 "Usage: queue remove member <channel> from <queue>\n";
 
+static char qgs_cmd_usage[] =
+"Usage: queue group show [groupname]\n"
+"       Shows information about group groupname. If no groupname is\n"
+"       specified, then all groups are shown\n";
+
 static struct ast_cli_entry cli_show_queue_deprecated = {
 	{ "show", "queue", NULL },
 	queue_show, NULL,
@@ -4871,6 +5126,10 @@
 	{ { "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 },
+
+	{ { "queue", "group", "show", NULL },
+	handle_queue_group_show, "Show status of specified queue group",
+	qgs_cmd_usage, complete_queue_group_show },
 };
 
 static int unload_module(void)
@@ -4892,6 +5151,7 @@
 	res |= ast_manager_unregister("QueueAdd");
 	res |= ast_manager_unregister("QueueRemove");
 	res |= ast_manager_unregister("QueuePause");
+	res |= ast_manager_unregister("QueueGroupShow");
 	res |= ast_unregister_application(app_aqm);
 	res |= ast_unregister_application(app_rqm);
 	res |= ast_unregister_application(app_pqm);
@@ -4937,6 +5197,7 @@
 	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_manager_register("QueueGroupShow", 0, manager_queue_group_show, "Queue group status");
 	res |= ast_custom_function_register(&queueagentcount_function);
 	res |= ast_custom_function_register(&queuemembercount_function);
 	res |= ast_custom_function_register(&queuememberlist_function);
Index: configs/queues.conf.sample
===================================================================
--- configs/queues.conf.sample	(revision 114901)
+++ configs/queues.conf.sample	(working copy)
@@ -278,6 +278,12 @@
 ;
 ; timeoutrestart = no
 ;
+; If a group is specified for a queue, then that group will be searched for in
+; queuegroups.conf. If a group is not found there, then it will be searched for in
+; realtime. For more information regarding queue groups, see queuegroups.conf.sample
+;
+; group = group1
+;
 ; Each member of this call queue is listed on a separate line in
 ; the form technology/dialstring.  "member" means a normal member of a
 ; queue.  An optional penalty may be specified after a comma, such that


